Can the Delay node rate limit, but pass first message immediately, and always pass on the latest message?

As author of the q-gate node, I want to help out here, but I have been too busy to follow the discussion in any depth. Based on the original post, I think this flow meets the requirements without the possibility of race conditions. (See this discussion and this one.)

[{"id":"67659f7786234008","type":"inject","z":"0ae9d1938fe1cb0d","name":"","props":[{"p":"payload"},{"p":"topic","vt":"str"}],"repeat":"2","crontab":"","once":false,"onceDelay":0.1,"topic":"","payload":"","payloadType":"date","x":130,"y":80,"wires":[["e8de0d8f5c30616b","92b84dfdf699239e"]]},{"id":"e8de0d8f5c30616b","type":"function","z":"0ae9d1938fe1cb0d","name":"queue after","func":"return [[msg,{payload:'queue',topic:'control'}]]","outputs":1,"noerr":0,"initialize":"","finalize":"","libs":[],"x":390,"y":80,"wires":[["c453ff7e92e4428d"]]},{"id":"92b84dfdf699239e","type":"trigger","z":"0ae9d1938fe1cb0d","name":"open after 5s","op1":"","op2":"open","op1type":"nul","op2type":"str","duration":"5","extend":false,"overrideDelay":false,"units":"s","reset":"","bytopic":"all","topic":"topic","outputs":1,"x":310,"y":120,"wires":[["1354ec7ffed7352c"]]},{"id":"c453ff7e92e4428d","type":"q-gate","z":"0ae9d1938fe1cb0d","name":"","controlTopic":"control","defaultState":"open","openCmd":"open","closeCmd":"close","toggleCmd":"toggle","queueCmd":"queue","defaultCmd":"default","triggerCmd":"trigger","flushCmd":"flush","resetCmd":"reset","peekCmd":"peek","dropCmd":"drop","statusCmd":"status","maxQueueLength":"1","keepNewest":true,"qToggle":false,"persist":false,"storeName":"memory","x":650,"y":80,"wires":[["1248d518fe22581f"]]},{"id":"1354ec7ffed7352c","type":"change","z":"0ae9d1938fe1cb0d","name":"topic:control","rules":[{"t":"set","p":"topic","pt":"msg","to":"control","tot":"str"}],"action":"","property":"","from":"","to":"","reg":false,"x":470,"y":120,"wires":[["c453ff7e92e4428d"]]},{"id":"1248d518fe22581f","type":"debug","z":"0ae9d1938fe1cb0d","name":"debug","active":true,"tosidebar":true,"console":false,"tostatus":false,"complete":"true","targetType":"full","statusVal":"","statusType":"auto","x":770,"y":80,"wires":[]}]

I could easily be mistaken, so if anyone can demonstrate a race condition here, either in theory or practice, please let me know.

Hi @drmibell, I think i does not meet the requirements.
Just clicking quickly on the trigger button I got msg quicker than 5s. Here is an example:

[{"id":"67659f7786234008","type":"inject","z":"8d7193deb1042133","name":"","props":[{"p":"payload"},{"p":"topic","vt":"str"}],"repeat":"","crontab":"","once":false,"onceDelay":0.1,"topic":"","payload":"","payloadType":"date","x":360,"y":3940,"wires":[["86fe5414cef4edd6","9b9b63b53aa3f5cc","d46cb9e075df54a5"]]},{"id":"e8de0d8f5c30616b","type":"function","z":"8d7193deb1042133","name":"queue after","func":"return [[msg,{payload:'queue',topic:'control'}]]","outputs":1,"noerr":0,"initialize":"","finalize":"","libs":[],"x":710,"y":3960,"wires":[["c453ff7e92e4428d"]]},{"id":"92b84dfdf699239e","type":"trigger","z":"8d7193deb1042133","name":"open after 5s","op1":"","op2":"open","op1type":"nul","op2type":"str","duration":"5","extend":false,"overrideDelay":false,"units":"s","reset":"","bytopic":"all","topic":"topic","outputs":1,"x":630,"y":4000,"wires":[["1354ec7ffed7352c"]]},{"id":"c453ff7e92e4428d","type":"q-gate","z":"8d7193deb1042133","name":"","controlTopic":"control","defaultState":"open","openCmd":"open","closeCmd":"close","toggleCmd":"toggle","queueCmd":"queue","defaultCmd":"default","triggerCmd":"trigger","flushCmd":"flush","resetCmd":"reset","peekCmd":"peek","dropCmd":"drop","statusCmd":"status","maxQueueLength":"1","keepNewest":true,"qToggle":false,"persist":false,"storeName":"memory","x":970,"y":3960,"wires":[["1248d518fe22581f"]]},{"id":"1354ec7ffed7352c","type":"change","z":"8d7193deb1042133","name":"topic:control","rules":[{"t":"set","p":"topic","pt":"msg","to":"control","tot":"str"}],"action":"","property":"","from":"","to":"","reg":false,"x":790,"y":4000,"wires":[["c453ff7e92e4428d"]]},{"id":"1248d518fe22581f","type":"debug","z":"8d7193deb1042133","name":"","active":true,"tosidebar":true,"console":false,"tostatus":false,"complete":"payload","targetType":"msg","statusVal":"","statusType":"auto","x":1210,"y":3960,"wires":[]},{"id":"9b9b63b53aa3f5cc","type":"trigger","z":"8d7193deb1042133","name":"4s","op1":"","op2":"","op1type":"nul","op2type":"date","duration":"4","extend":false,"overrideDelay":false,"units":"s","reset":"","bytopic":"all","topic":"topic","outputs":1,"x":350,"y":3980,"wires":[["86fe5414cef4edd6"]]},{"id":"d46cb9e075df54a5","type":"trigger","z":"8d7193deb1042133","name":"5001 ms","op1":"","op2":"","op1type":"nul","op2type":"date","duration":"5001","extend":false,"overrideDelay":false,"units":"ms","reset":"","bytopic":"all","topic":"topic","outputs":1,"x":360,"y":4020,"wires":[["86fe5414cef4edd6"]]},{"id":"86fe5414cef4edd6","type":"junction","z":"8d7193deb1042133","x":520,"y":3960,"wires":[["e8de0d8f5c30616b","92b84dfdf699239e"]]}]

Quickly tested this scenario with the state machine and it passed.

Edit: @cameo69 beat me to it...

I'm afraid that does not meet the specification...

Messages are injected at 0s, 1s and 7s. They should reach the output at 0s, 5s and 10s but you can see MSGC is not queued at all.

Finite state version: For me the Monaco editor complains about line 20 of smxstate and I get a "Confirm deploy" dialog.
Untitled 3

Are you sure? The first message has timestamp 1662327794341 and the second is 1662327799343. The difference is 5002 ms, almost exactly 5 s. I can't explain why the debug panel shows the times as identical.

Could you check again, including timestamps in the messages? After @cameo69's result, I would not trust the times displayed in the debug sidebar.

But I saw this issue in the past. If I remember correctly, reopening the editor and hitting ok again, makes it work. Not sure why it does that.

However, if you get it to run...
I just tested with the scenario of msg triggered after 0s, 1s, 4999ms, and 5001ms and the result looks according to spec to me:
2nd msg dropped and the other 3 all exactly 5s apart.

[{"id":"04b64927acef2482","type":"smxstate","z":"8d7193deb1042133","name":"","xstateDefinition":"// Available variables/objects/functions:\n// xstate\n// - .Machine\n// - .interpret\n// - .assign\n// - .send\n// - .sendParent\n// - .spawn\n// - .raise\n// - .actions\n//\n// Common\n// - setInterval, setTimeout, clearInterval, clearTimeout\n// - node.send, node.warn, node.log, node.error\n// - context.get, context.set\n// - flow.get, flow.set\n// - env.get\n// - util\n\nconst { assign } = xstate;\n\n// First define names guards, actions, ...\n\n/**\n * Guards\n */\nconst isCounterZero = (context, event) => {\n  return context.counter == 0;\n};\n\n/**\n * Actions\n */\nconst incrementCounter = assign({\n  counter: (context, event) => 1\n});\n\nconst zeroCounter = assign({\n  counter: (context, event) => 0\n});\n\n\nconst sendTrigger = assign({\n  counter: (context, event) => {\n    // Can send log messages via\n    //  - node.log\n    //  - node.warn\n    //  - node.error\n    //node.warn(\"RESET\");\n\n    // Can send messages to the second outport\n    // Specify an array to send multiple messages\n    // at once\n    //  - node.send(msg)\n    node.send({ topic: \"control\", payload: \"trigger\" });\n\n    return 0;\n  }\n});\n\n\n/***************************\n * Main machine definition * \n ***************************/\nreturn {\n  machine: {\n    predictableActionArguments: true,\n    context: {\n      counter: 0\n    },\n    initial: 'idle',\n    states: {\n      idle: {\n        on: {\n          incoming: { target: 'trigger' }\n        }\n      },\n      trigger: {\n        entry: ['sendTrigger', 'zeroCounter'],\n        on: {\n          incoming: { actions: 'incrementCounter' },\n          '': { target: 'wait' }\n        }\n      },\n      wait: {\n        on: {\n          incoming: { actions: 'incrementCounter' }\n        },\n        after: {\n          5000: [{ target: 'last', cond: 'isCounterZero' },\n          { target: 'trigger' }]\n        }\n      },\n      last: {\n        entry: ['sendTrigger', 'zeroCounter'],\n        on: {\n          incoming: { actions: 'incrementCounter' },\n          '': [{ target: 'idle', cond: 'isCounterZero' },\n          { target: 'wait' }]\n        }\n      }\n    }\n  },\n  // Configuration containing guards, actions, activities, ...\n  // see above\n  config: {\n    guards: { isCounterZero },\n    actions: { incrementCounter, sendTrigger, zeroCounter },\n    activities: {}\n  },\n  // Define listeners (can be an array of functions)\n  //    Functions get called on every state/context update\n  listeners: (data) => {\n    //node.warn(data.state + \":\" + data.context.counter);\n  }\n};","noerr":0,"x":1040,"y":4160,"wires":[[],["5d128a914d06ebb1"]]},{"id":"3d45b6ba8dd81cc5","type":"debug","z":"8d7193deb1042133","name":"","active":true,"tosidebar":true,"console":false,"tostatus":false,"complete":"payload","targetType":"msg","statusVal":"","statusType":"auto","x":1190,"y":4260,"wires":[]},{"id":"5d128a914d06ebb1","type":"q-gate","z":"8d7193deb1042133","name":"","controlTopic":"control","defaultState":"queueing","openCmd":"open","closeCmd":"close","toggleCmd":"toggle","queueCmd":"queue","defaultCmd":"default","triggerCmd":"trigger","flushCmd":"flush","resetCmd":"reset","peekCmd":"peek","dropCmd":"drop","statusCmd":"status","maxQueueLength":"1","keepNewest":true,"qToggle":false,"persist":false,"storeName":"memory","x":850,"y":4260,"wires":[["87d228379f239025"]]},{"id":"91512a5c91c39de0","type":"change","z":"8d7193deb1042133","name":"topic = incoming","rules":[{"t":"set","p":"topic","pt":"msg","to":"incoming","tot":"str"}],"action":"","property":"","from":"","to":"","reg":false,"x":830,"y":4160,"wires":[["04b64927acef2482"]]},{"id":"099c9e6445d82a96","type":"inject","z":"8d7193deb1042133","name":"","props":[{"p":"payload"},{"p":"topic","vt":"str"}],"repeat":"","crontab":"","once":false,"onceDelay":0.1,"topic":"","payload":"","payloadType":"date","x":500,"y":4160,"wires":[["497b49087350269d","4dd5dd802568b2e8","cfe2538c6ad7a1cd","5273dd0d2bd484c2"]]},{"id":"cfe2538c6ad7a1cd","type":"trigger","z":"8d7193deb1042133","name":"4999 ms","op1":"","op2":"","op1type":"nul","op2type":"date","duration":"4999","extend":false,"overrideDelay":false,"units":"ms","reset":"","bytopic":"all","topic":"topic","outputs":1,"x":500,"y":4240,"wires":[["497b49087350269d"]]},{"id":"5273dd0d2bd484c2","type":"trigger","z":"8d7193deb1042133","name":"5001 ms","op1":"","op2":"","op1type":"nul","op2type":"date","duration":"5001","extend":false,"overrideDelay":false,"units":"ms","reset":"","bytopic":"all","topic":"topic","outputs":1,"x":500,"y":4280,"wires":[["497b49087350269d"]]},{"id":"4dd5dd802568b2e8","type":"trigger","z":"8d7193deb1042133","name":"1 s","op1":"","op2":"","op1type":"nul","op2type":"date","duration":"1","extend":false,"overrideDelay":false,"units":"s","reset":"","bytopic":"all","topic":"topic","outputs":1,"x":490,"y":4200,"wires":[["497b49087350269d"]]},{"id":"87d228379f239025","type":"change","z":"8d7193deb1042133","name":"format date","rules":[{"t":"set","p":"payload","pt":"msg","to":"$fromMillis(\t   msg.payload,\t   '[Y0001]-[M01]-[D01] [H01]:[m01]:[s01].[f001] [z]',\t   '+0200'\t)","tot":"jsonata"}],"action":"","property":"","from":"","to":"","reg":false,"x":1010,"y":4260,"wires":[["3d45b6ba8dd81cc5"]]},{"id":"497b49087350269d","type":"junction","z":"8d7193deb1042133","x":660,"y":4180,"wires":[["5d128a914d06ebb1","91512a5c91c39de0"]]}]

maybe my injects are faulty? It's still NAS.

[{"id":"e8de0d8f5c30616b","type":"function","z":"864772ef56f82916","name":"queue after","func":"return [[msg,{payload:'queue',topic:'control'}]]","outputs":1,"noerr":0,"initialize":"","finalize":"","libs":[],"x":450,"y":2600,"wires":[["c453ff7e92e4428d"]]},{"id":"92b84dfdf699239e","type":"trigger","z":"864772ef56f82916","name":"open after 5s","op1":"","op2":"open","op1type":"nul","op2type":"str","duration":"5","extend":false,"overrideDelay":false,"units":"s","reset":"","bytopic":"all","topic":"topic","outputs":1,"x":450,"y":2640,"wires":[["1354ec7ffed7352c"]]},{"id":"c453ff7e92e4428d","type":"q-gate","z":"864772ef56f82916","name":"","controlTopic":"control","defaultState":"open","openCmd":"open","closeCmd":"close","toggleCmd":"toggle","queueCmd":"queue","defaultCmd":"default","triggerCmd":"trigger","flushCmd":"flush","resetCmd":"reset","peekCmd":"peek","dropCmd":"drop","statusCmd":"status","maxQueueLength":"1","keepNewest":true,"qToggle":false,"persist":false,"storeName":"memory","x":790,"y":2600,"wires":[["1248d518fe22581f"]]},{"id":"1354ec7ffed7352c","type":"change","z":"864772ef56f82916","name":"topic:control","rules":[{"t":"set","p":"topic","pt":"msg","to":"control","tot":"str"}],"action":"","property":"","from":"","to":"","reg":false,"x":610,"y":2640,"wires":[["c453ff7e92e4428d"]]},{"id":"1248d518fe22581f","type":"debug","z":"864772ef56f82916","name":"debug","active":true,"tosidebar":true,"console":false,"tostatus":false,"complete":"true","targetType":"full","statusVal":"","statusType":"auto","x":910,"y":2600,"wires":[]},{"id":"2a7d8d22c936c38b","type":"inject","z":"864772ef56f82916","name":"","props":[{"p":"payload"}],"repeat":"","crontab":"","once":false,"onceDelay":0.1,"topic":"","payload":"","payloadType":"date","x":160,"y":2620,"wires":[["c61aa5e9f06f9b14","43e0bff84e57f515","f17e5102d9e25697"]]},{"id":"c61aa5e9f06f9b14","type":"trigger","z":"864772ef56f82916","name":"1s timestamp","op1":"","op2":"","op1type":"nul","op2type":"date","duration":"1","extend":false,"overrideDelay":false,"units":"s","reset":"","bytopic":"all","topic":"topic","outputs":1,"x":150,"y":2660,"wires":[["f17e5102d9e25697"]]},{"id":"43e0bff84e57f515","type":"trigger","z":"864772ef56f82916","name":"7s timestamp","op1":"","op2":"","op1type":"nul","op2type":"date","duration":"7","extend":false,"overrideDelay":false,"units":"s","reset":"","bytopic":"all","topic":"topic","outputs":1,"x":150,"y":2580,"wires":[["f17e5102d9e25697"]]},{"id":"f17e5102d9e25697","type":"junction","z":"864772ef56f82916","x":320,"y":2600,"wires":[["e8de0d8f5c30616b","92b84dfdf699239e"]]}]

I got the FSM working by deleting, restarting Node-red and reimporting.

I think there's a problem if you inject MSGA at 0s and MSGB at 1ms - it outputs MSGB instead of MSGA

I believe this is the correct result. As I understand the spec (as written, if not as intended), the logic is based on the time of arrival of the messages. So it should be:

MSG A arrives t = 0, departs t =0
MSG B arrives t = 1, departs t = 5 (arrived less than 5s after MSG A)
MSG C arrives t = 7, departs t = 7 (arrived more than 5s after MSG B)

If MSG C had arrived before t = 5, it would have replaced MSG B in the queue and been released at t = 5.

In spite of the title of the topic, the specification, as I read it, does not actually rate limit in the sense of sending messages no faster than one every 5 s.

Nice one. I would quickly solve it with a 2nd queue, however it get's messy if you go down to milliseconds. Maybe we need @Colin to tell us that his requirements are met at some point :slight_smile:

The top q-gate handles the first message while the subsequent msg each 5s are handled by the bottom q-gate:

output3

[{"id":"28aa7c98653b5ade","type":"smxstate","z":"1ca0617cfc39c447","name":"","xstateDefinition":"// Available variables/objects/functions:\n// xstate\n// - .Machine\n// - .interpret\n// - .assign\n// - .send\n// - .sendParent\n// - .spawn\n// - .raise\n// - .actions\n//\n// Common\n// - setInterval, setTimeout, clearInterval, clearTimeout\n// - node.send, node.warn, node.log, node.error\n// - context.get, context.set\n// - flow.get, flow.set\n// - env.get\n// - util\n\nconst { assign } = xstate;\n\n// First define names guards, actions, ...\n\n/**\n * Guards\n */\nconst isCounterZero = (context, event) => {\n  return context.counter == 0;\n};\n\n/**\n * Actions\n */\nconst incrementCounter = assign({\n  counter: (context, event) => 1\n});\n\nconst zeroCounter = assign({\n  counter: (context, event) => 0\n});\n\n\nconst sendTrigger1st = assign({\n  counter: (context, event) => {\n    // Can send log messages via\n    //  - node.log\n    //  - node.warn\n    //  - node.error\n    //node.warn(\"RESET\");\n\n    // Can send messages to the second outport\n    // Specify an array to send multiple messages\n    // at once\n    //  - node.send(msg)\n    node.send({ topic: \"control\", payload: \"trigger\", queue: \"1\" });\n\n    return 0;\n  }\n});\n\nconst sendTrigger = assign({\n  counter: (context, event) => {\n    node.send({ topic: \"control\", payload: \"trigger\", queue: \"n\" });\n    return 0;\n  }\n});\n\nconst sendReset = (context, event) => {\n  node.send({ topic: \"control\", payload: \"reset\", queue: \"1\" });\n};\n\n/*const sendReset = assign({\n  counter: (context, event) => {\n    node.send({ topic: \"control\", payload: \"reset\", queue: \"1\" });\n    return 0;\n  }\n});*/\n\n/***************************\n * Main machine definition * \n ***************************/\nreturn {\n  machine: {\n    predictableActionArguments: true,\n    context: {\n      counter: 0\n    },\n    initial: 'idle',\n    states: {\n      idle: {\n        on: {\n          incoming: { target: 'trigger1st' }\n        }\n      },\n\n      trigger1st: {\n        entry: ['sendTrigger1st'], \n        on: {\n          incoming: { actions: 'incrementCounter' },\n          '': { target: 'wait' }\n        }\n      },\n\n      trigger: {\n        entry: ['sendTrigger', 'sendReset'], \n        on: {\n          incoming: { actions: 'incrementCounter' },\n          '': { target: 'wait' }\n        }\n      },\n\n      wait: {\n        on: {\n          incoming: { actions: 'incrementCounter' }\n        },\n        after: {\n          5000: [{ target: 'last', cond: 'isCounterZero' },\n          { target: 'trigger' }]\n        }\n      },\n      \n      last: {\n        entry: ['sendTrigger', 'sendReset'],\n        on: {\n          incoming: { actions: 'incrementCounter' },\n          '': [{ target: 'idle', cond: 'isCounterZero' },\n          { target: 'wait' }]\n        }\n      }\n    }\n  },\n  // Configuration containing guards, actions, activities, ...\n  // see above\n  config: {\n    guards: { isCounterZero },\n    actions: { incrementCounter, sendTrigger1st, sendTrigger, sendReset, zeroCounter },\n    activities: {}\n  },\n  // Define listeners (can be an array of functions)\n  //    Functions get called on every state/context update\n  listeners: (data) => {\n    //node.warn(data.state + \":\" + data.context.counter);\n  }\n};","noerr":0,"x":1040,"y":120,"wires":[[],["0b8419d044eab106"]]},{"id":"9b7c97ab93743ea8","type":"debug","z":"1ca0617cfc39c447","name":"","active":true,"tosidebar":true,"console":false,"tostatus":false,"complete":"payload","targetType":"msg","statusVal":"","statusType":"auto","x":1190,"y":240,"wires":[]},{"id":"2515695b9cfde286","type":"q-gate","z":"1ca0617cfc39c447","name":"","controlTopic":"control","defaultState":"queueing","openCmd":"open","closeCmd":"close","toggleCmd":"toggle","queueCmd":"queue","defaultCmd":"default","triggerCmd":"trigger","flushCmd":"flush","resetCmd":"reset","peekCmd":"peek","dropCmd":"drop","statusCmd":"status","maxQueueLength":"1","keepNewest":true,"qToggle":false,"persist":false,"storeName":"memory","x":1010,"y":280,"wires":[["9b7c97ab93743ea8"]]},{"id":"47f5f8d04e6c4a34","type":"change","z":"1ca0617cfc39c447","name":"topic = incoming","rules":[{"t":"set","p":"topic","pt":"msg","to":"incoming","tot":"str"}],"action":"","property":"","from":"","to":"","reg":false,"x":830,"y":120,"wires":[["28aa7c98653b5ade"]]},{"id":"5ef19fcc9a17e6a5","type":"inject","z":"1ca0617cfc39c447","name":"","props":[{"p":"payload"}],"repeat":"","crontab":"","once":false,"onceDelay":0.1,"topic":"","payload":"A","payloadType":"str","x":410,"y":60,"wires":[["afb3d663aed398f1","39c6a2b22ec03ff5","50b73ee76d6bb8b3","ccc628e5b6d03651","56817045834ff8df"]]},{"id":"50b73ee76d6bb8b3","type":"trigger","z":"1ca0617cfc39c447","name":"4999 ms: D","op1":"","op2":"D","op1type":"nul","op2type":"str","duration":"4999","extend":false,"overrideDelay":false,"units":"ms","reset":"","bytopic":"all","topic":"topic","outputs":1,"x":430,"y":180,"wires":[["afb3d663aed398f1"]]},{"id":"ccc628e5b6d03651","type":"trigger","z":"1ca0617cfc39c447","name":"5010 ms: E","op1":"","op2":"E","op1type":"nul","op2type":"str","duration":"5010","extend":false,"overrideDelay":false,"units":"ms","reset":"","bytopic":"all","topic":"topic","outputs":1,"x":430,"y":220,"wires":[["afb3d663aed398f1"]]},{"id":"39c6a2b22ec03ff5","type":"trigger","z":"1ca0617cfc39c447","name":"1 s: C","op1":"","op2":"C","op1type":"nul","op2type":"str","duration":"1","extend":false,"overrideDelay":false,"units":"s","reset":"","bytopic":"all","topic":"topic","outputs":1,"x":410,"y":140,"wires":[["afb3d663aed398f1"]]},{"id":"a6c4cdaf8b7d762e","type":"inject","z":"1ca0617cfc39c447","name":"","props":[{"p":"payload"}],"repeat":"","crontab":"","once":false,"onceDelay":0.1,"topic":"","payload":"","payloadType":"date","x":440,"y":380,"wires":[["dbdc380df1758faa","a9b3a54c09892c40","197b84696c4e2168"]]},{"id":"dbdc380df1758faa","type":"trigger","z":"1ca0617cfc39c447","name":"1s timestamp","op1":"","op2":"","op1type":"nul","op2type":"date","duration":"1","extend":false,"overrideDelay":false,"units":"s","reset":"","bytopic":"all","topic":"topic","outputs":1,"x":430,"y":420,"wires":[["197b84696c4e2168"]]},{"id":"a9b3a54c09892c40","type":"trigger","z":"1ca0617cfc39c447","name":"7s timestamp","op1":"","op2":"","op1type":"nul","op2type":"date","duration":"7","extend":false,"overrideDelay":false,"units":"s","reset":"","bytopic":"all","topic":"topic","outputs":1,"x":430,"y":340,"wires":[["197b84696c4e2168"]]},{"id":"d6a95012f5720d8a","type":"q-gate","z":"1ca0617cfc39c447","name":"","controlTopic":"control","defaultState":"queueing","openCmd":"open","closeCmd":"close","toggleCmd":"toggle","queueCmd":"queue","defaultCmd":"default","triggerCmd":"trigger","flushCmd":"flush","resetCmd":"reset","peekCmd":"peek","dropCmd":"drop","statusCmd":"status","maxQueueLength":"0","keepNewest":true,"qToggle":false,"persist":false,"storeName":"memory","x":1010,"y":220,"wires":[["9b7c97ab93743ea8"]]},{"id":"56817045834ff8df","type":"trigger","z":"1ca0617cfc39c447","name":"1 ms: B","op1":"","op2":"B","op1type":"nul","op2type":"str","duration":"1","extend":false,"overrideDelay":false,"units":"ms","reset":"","bytopic":"all","topic":"topic","outputs":1,"x":420,"y":100,"wires":[["afb3d663aed398f1"]]},{"id":"0b8419d044eab106","type":"switch","z":"1ca0617cfc39c447","name":"switch queue","property":"queue","propertyType":"msg","rules":[{"t":"eq","v":"1","vt":"str"},{"t":"eq","v":"n","vt":"str"}],"checkall":"false","repair":false,"outputs":2,"x":770,"y":260,"wires":[["d6a95012f5720d8a"],["2515695b9cfde286"]]},{"id":"afb3d663aed398f1","type":"junction","z":"1ca0617cfc39c447","x":660,"y":140,"wires":[["2515695b9cfde286","47f5f8d04e6c4a34","d6a95012f5720d8a"]]},{"id":"197b84696c4e2168","type":"junction","z":"1ca0617cfc39c447","x":600,"y":360,"wires":[["afb3d663aed398f1"]]}]

MSG C does NOT arrive more than 5s after MSG B (t=7 minus t=5 equals 2s).

This clearly violates requirement 2:

Sorry, but MSG B arrived arrived at t = 1 and MSG C arrived at t = 7 (7 - 1 = 6). You are confusing arrival and departure times. When the requirement says "second message comes in less than five seconds after the first," (my emphasis), it is clearly talking about arrival times. Hence my comment about not actually rate limiting outgoing messages.

I bet Colin is really glad he raised this question in the first place! :rofl:

You make a good point about the 6s between MSG B and C.
I didn't interpret it like that, probably giving more weight to "So the purpose is to rate limit".

Either it's "No messages should be emitted at less than 5sec intervals" or "There must be at least 5sec between emitting the first and any subsequent message".
Who knows?

Sorry for being AWOL for a time. Real life took priority.

It is the former, there must be at least 5 seconds interval between every message sent.
As I said in the first post, it is nearly achievable with the Delay node in Rate Limit, and Each msg.topic mode. In that mode it queues the most recent message for each topic and releases it at the next time interval. It does not satisfy the requirement that if more than five seconds has elapsed since the last message was sent then one coming in should be passed on immediately. So another way to look at the spec is the addition of a feature to the Delay node that in All Messages mode there is an option to queue the most recent message rather than just discard them all.

The reason for the question (as mentioned earlier) was to help with the previously linked thread. In fact that was fixed satisfactorily with one of the earlier suggestions, as the possible race conditions did not matter for that specific application. It then became a matter of intellectual interest, to see whether it could be reasonably easily solved without resorting to a function node (or DSM node), plus I can see that it does have uses (as in the other thread). I think it is clear that, though it may well be possible with nodes, any solution will be rather complex and difficult to understand, so I think probably a Function node is the best way to go. Alternatively an enhancement to the Delay node would be good to add this feature. Unfortunately I haven't got into the development of core nodes so would need to spend some time getting that sorted, and I just don't have the time at the moment.

Having noodled this a while I think this does the trick with standard nodes

[{"id":"ce4b2f27d2d181d8","type":"inject","z":"1f7f9c4efc1c5c0a","name":"","props":[{"p":"payload"},{"p":"topic","vt":"str"}],"repeat":"","crontab":"","once":false,"onceDelay":0.1,"topic":"A","payload":"1","payloadType":"num","x":220,"y":780,"wires":[["5e554059014ec7f2"]]},{"id":"8c004dc7912dc3ac","type":"trigger","z":"1f7f9c4efc1c5c0a","name":"","op1":"","op2":"","op1type":"nul","op2type":"payl","duration":"5","extend":true,"overrideDelay":false,"units":"s","reset":"","bytopic":"all","topic":"topic","outputs":1,"x":480,"y":885,"wires":[["5e554059014ec7f2"]]},{"id":"d88cc3cace4cda48","type":"inject","z":"1f7f9c4efc1c5c0a","name":"","props":[{"p":"payload"},{"p":"topic","vt":"str"}],"repeat":"","crontab":"","once":false,"onceDelay":0.1,"topic":"A","payload":"2","payloadType":"num","x":220,"y":825,"wires":[["5e554059014ec7f2"]]},{"id":"87acfb08b3f28e66","type":"inject","z":"1f7f9c4efc1c5c0a","name":"","props":[{"p":"payload"},{"p":"topic","vt":"str"}],"repeat":"","crontab":"","once":false,"onceDelay":0.1,"topic":"A","payload":"3","payloadType":"num","x":220,"y":870,"wires":[["5e554059014ec7f2"]]},{"id":"8317fdb6d1bae677","type":"debug","z":"1f7f9c4efc1c5c0a","name":"debug 3","active":true,"tosidebar":true,"console":false,"tostatus":false,"complete":"false","statusVal":"","statusType":"auto","x":720,"y":780,"wires":[]},{"id":"5e554059014ec7f2","type":"delay","z":"1f7f9c4efc1c5c0a","name":"","pauseType":"rate","timeout":"5","timeoutUnits":"seconds","rate":"1","nbRateUnits":"5","rateUnits":"second","randomFirst":"1","randomLast":"5","randomUnits":"seconds","drop":true,"allowrate":false,"outputs":2,"x":475,"y":780,"wires":[["8317fdb6d1bae677"],["8c004dc7912dc3ac"]]}]

Consider
Msg 1 arrives at t =0 and gets passed on
Msg 2 arrives at t=4 and gets passed to the Trigger
Msg 3 arrives at t=6 and gets passed on
At t=9 the trigger runs out, passes msg 2 to the Delay node, which sends it to the trigger again.
At t=14 the trigger runs down and msg 2 gets sent to the Delay node and passed on.
So the outputs are
t=0 msg 1
t=6 msg 3
t=14 msg 2

drat... !?!?*&^!?

1 Like

I think the best solution is definitely to add this feature to the Delay node as is being discussed in this post.

1 Like

This topic was automatically closed 60 days after the last reply. New replies are no longer allowed.