Timing question (avoiding race conditions)

In a recent discussion, I proposed a flow that required sending two messages in rapid succession, the first to initialize the flow and the second to set the state of certain nodes. The obvious way is to use a function node containing just (for example):

return [[{payload:'initialize', topic:'control'},msg]]

where the incoming message (possibly from an inject node) is something like

msg = {payload:'changeState`, topic:'control'}. 

I have a few questions about this.

  • Is it safe to assume that the first downstream node will receive both messages before getting messages from any other source? In other words, if no asynchronous processes are involved, will it be in the desired state before new messages arrive?
  • Can an inject node be configured to send the required pair of messages? I haven't found a way to do this.
  • If a trigger node is used, can the interval between messages be set short enough to guarantee that nothing arrives in between the two?

I realize that in the spirit of "if it ain't broke, don't fix it", I probably have a working solution, but coming from a background in system control, I still get queasy about things that can go asynchronous and non-deterministic...

Try this
1 should follow 2
3 can come any time
4 and 5 are sent at same time
6 an7 are sent 1 millisecond apart

it seems 1,2 and 4,5 pair up correctly

[{"id":"8be22f30.d15428","type":"inject","z":"c791cbc0.84f648","name":"test race","props":[{"p":"payload"},{"p":"topic","v":"false","vt":"bool"}],"repeat":"","crontab":"","once":false,"onceDelay":0.1,"topic":"","payload":"","payloadType":"date","x":160,"y":1560,"wires":[["26398477.34fec4","660c5560.772334","22efed7a.0eb59a","5ea3f2ef.26249c"]]},{"id":"26398477.34fec4","type":"function","z":"c791cbc0.84f648","name":"randomise sending","func":"for( let i =0; i<20; i++){\n    let number = i+1;\n   setTimeout(() => {node.send(Object.assign(msg,{number}))}, Math.floor((Math.random() * 100) + 1));\n}\nreturn null;","outputs":1,"noerr":0,"initialize":"","finalize":"","x":170,"y":1620,"wires":[["15379e66.503202"]]},{"id":"660c5560.772334","type":"function","z":"c791cbc0.84f648","name":"randomise sending","func":"for( let i =0; i<20; i++){\n    let number = i+1;\n   setTimeout(() => {node.send(Object.assign(msg,{number}))}, Math.floor((Math.random() * 100) + 1));\n}\nreturn null;","outputs":1,"noerr":0,"initialize":"","finalize":"","x":170,"y":1660,"wires":[["f849c5d3.1c5b88"]]},{"id":"22efed7a.0eb59a","type":"function","z":"c791cbc0.84f648","name":"randomise sending","func":"for( let i =0; i<20; i++){\n    let number = i+1;\n   setTimeout(() => {node.send(Object.assign(msg,{number}))}, Math.floor((Math.random() * 100) + 1));\n}\nreturn null;","outputs":1,"noerr":0,"initialize":"","finalize":"","x":170,"y":1720,"wires":[["7c036555.65393c","29b8453f.b97e02"]]},{"id":"5ea3f2ef.26249c","type":"function","z":"c791cbc0.84f648","name":"randomise sending","func":"for( let i =0; i<20; i++){\n    let number = i+1;\n   setTimeout(() => {node.send(Object.assign(msg,{number}))}, Math.floor((Math.random() * 100) + 1));\n}\nreturn null;","outputs":1,"noerr":0,"initialize":"","finalize":"","x":170,"y":1820,"wires":[["94fe0b88.effc6","f97176ac.df797"]]},{"id":"15379e66.503202","type":"change","z":"c791cbc0.84f648","name":"1 and 2","rules":[{"t":"set","p":"payload","pt":"msg","to":"[\t   {\t       \"payload\":\"initialize\",\t       \"topic\":\"control\",\t       \"number\":\"1 - \" & number\t   },\t   {\t       \"payload\":\"changeState\",\t       \"topic\":\"control\",\t       \"number\":\"2 - \" & number\t   }\t]","tot":"jsonata"}],"action":"","property":"","from":"","to":"","reg":false,"x":400,"y":1620,"wires":[["e0fe9f92.abeff8"]]},{"id":"f849c5d3.1c5b88","type":"change","z":"c791cbc0.84f648","name":"3","rules":[{"t":"set","p":"payload","pt":"msg","to":"changeState","tot":"str"},{"t":"set","p":"payload","pt":"msg","to":"control","tot":"str"},{"t":"set","p":"number","pt":"msg","to":"\"3 - \" & number","tot":"jsonata"}],"action":"","property":"","from":"","to":"","reg":false,"x":390,"y":1660,"wires":[["e0fe9f92.abeff8"]]},{"id":"7c036555.65393c","type":"change","z":"c791cbc0.84f648","name":"4","rules":[{"t":"set","p":"payload","pt":"msg","to":"changeState","tot":"str"},{"t":"set","p":"payload","pt":"msg","to":"control","tot":"str"},{"t":"set","p":"number","pt":"msg","to":"\"4 - \" & number","tot":"jsonata"}],"action":"","property":"","from":"","to":"","reg":false,"x":390,"y":1720,"wires":[["e0fe9f92.abeff8"]]},{"id":"29b8453f.b97e02","type":"change","z":"c791cbc0.84f648","name":"5","rules":[{"t":"set","p":"payload","pt":"msg","to":"changeState","tot":"str"},{"t":"set","p":"payload","pt":"msg","to":"control","tot":"str"},{"t":"set","p":"number","pt":"msg","to":"\"5 - \" & number","tot":"jsonata"}],"action":"","property":"","from":"","to":"","reg":false,"x":390,"y":1760,"wires":[["e0fe9f92.abeff8"]]},{"id":"94fe0b88.effc6","type":"change","z":"c791cbc0.84f648","name":"6","rules":[{"t":"set","p":"payload","pt":"msg","to":"changeState","tot":"str"},{"t":"set","p":"payload","pt":"msg","to":"control","tot":"str"},{"t":"set","p":"number","pt":"msg","to":"\"6 - \" & number","tot":"jsonata"}],"action":"","property":"","from":"","to":"","reg":false,"x":390,"y":1820,"wires":[["e0fe9f92.abeff8"]]},{"id":"f97176ac.df797","type":"delay","z":"c791cbc0.84f648","name":"","pauseType":"delay","timeout":"1","timeoutUnits":"milliseconds","rate":"1","nbRateUnits":"1","rateUnits":"second","randomFirst":"1","randomLast":"5","randomUnits":"seconds","drop":false,"x":250,"y":1860,"wires":[["8f44aafb.e84dc8"]]},{"id":"e0fe9f92.abeff8","type":"function","z":"c791cbc0.84f648","name":"split array with no topic","func":"if (Array.isArray(msg.payload) && !msg.topic){\n    msg = [msg.payload]\n}\nreturn msg;","outputs":1,"noerr":0,"initialize":"","finalize":"","x":690,"y":1660,"wires":[["ae9bcb92.26cb38"]]},{"id":"8f44aafb.e84dc8","type":"change","z":"c791cbc0.84f648","name":"7","rules":[{"t":"set","p":"payload","pt":"msg","to":"changeState","tot":"str"},{"t":"set","p":"payload","pt":"msg","to":"control","tot":"str"},{"t":"set","p":"number","pt":"msg","to":"\"7 - \" & number","tot":"jsonata"}],"action":"","property":"","from":"","to":"","reg":false,"x":390,"y":1860,"wires":[["e0fe9f92.abeff8"]]},{"id":"ae9bcb92.26cb38","type":"change","z":"c791cbc0.84f648","name":"","rules":[{"t":"move","p":"number","pt":"msg","to":"payload","tot":"msg"}],"action":"","property":"","from":"","to":"","reg":false,"x":670,"y":1740,"wires":[["ae15bf43.7500d"]]},{"id":"ae15bf43.7500d","type":"debug","z":"c791cbc0.84f648","name":"","active":true,"tosidebar":true,"console":false,"tostatus":false,"complete":"payload","targetType":"msg","statusVal":"","statusType":"auto","x":650,"y":1780,"wires":[]}]

the output is
number sent - order sent

Thanks. It took me a while to get my head around the message (pun intended) that you were sending with that flow, but I think I see it. I tried a few similar experiments and convinced myself that if a function node sends an array of messages, the following node will see them in sequence and cannot accept any messages in between. I was hoping that someone with an understanding of NR internals and the node.js event loop could assure me that this is always true. I am still looking for ways to generate similar message sequences from inject or trigger nodes, but with no success.

1,2 are sent in an array, if you use the split array with no topic function before your nodes then it will pass single messages as normal. If it receives an array with no topic(or set a specific topic) and it is an array, then it will split them. The function should pass the array of commands before passing any other incoming messages
[edit]
as node red is single threaded i do not think it will cause race conditions. The node-red team will correct me if I am wrong I hope.

I agree with your edit. I apologize if my question was not entirely clear. This "flow" tries to capture my issue.

image

[{"id":"cb375306.0e1a38","type":"inject","z":"ed93693f.0b2c18","name":"inject","props":[{"p":"payload"}],"repeat":"","crontab":"","once":false,"onceDelay":0.1,"topic":"","payload":"","payloadType":"date","x":220,"y":660,"wires":[["100d232f.6f6ebd"]]},{"id":"100d232f.6f6ebd","type":"function","z":"ed93693f.0b2c18","name":"[msg0,msg1]","func":"msg0 = {payload:0}\nmsg1 = {payload:1}\nreturn [[msg0,msg1]];","outputs":1,"noerr":0,"initialize":"","finalize":"","x":370,"y":660,"wires":[["341d10d2.6707"]]},{"id":"341d10d2.6707","type":"function","z":"ed93693f.0b2c18","name":"process","func":"\nreturn msg;","outputs":1,"noerr":0,"initialize":"","finalize":"","x":540,"y":680,"wires":[["d3c8d390.e6ba38"]]},{"id":"65493f2f.6b808","type":"inject","z":"ed93693f.0b2c18","name":"msg2","props":[{"p":"payload"}],"repeat":"","crontab":"","once":false,"onceDelay":0.1,"topic":"","payload":"2","payloadType":"num","x":390,"y":700,"wires":[["341d10d2.6707"]]},{"id":"d3c8d390.e6ba38","type":"debug","z":"ed93693f.0b2c18","name":"","active":true,"tosidebar":true,"console":false,"tostatus":false,"complete":"true","targetType":"full","statusVal":"","statusType":"auto","x":670,"y":680,"wires":[]}]

The function node on the left sends the two messages msg0 and msg1 immediately. The inject node "msg2" represents another source that might deliver messages at any time. My assumption is that the second function node ("process") will never be asked to handle msg2 in between msg0 and msg1, regardless of when msg2 is sent. My simple-minded picture is that msg0 and msg1 are queued up for the following node in a single cycle of the node.js event loop, and no other messages can get into the queue between them. I hope that is essentially correct.

As shown by my experiment it would seem that as long as they are sent with no delay then they seem to run consecutively. My example of 1,2 as an array show that they run consecutive and can not be seperated before the split function, so you can be sure they run one after the other in the following process function.
here is an example

[{"id":"cb375306.0e1a38","type":"inject","z":"c791cbc0.84f648","name":"inject","props":[{"p":"payload"}],"repeat":"","crontab":"","once":false,"onceDelay":0.1,"topic":"","payload":"","payloadType":"date","x":90,"y":2640,"wires":[["100d232f.6f6ebd"]]},{"id":"100d232f.6f6ebd","type":"function","z":"c791cbc0.84f648","name":"[{msg1},{msg2}]","func":"msg.payload = [{payload:0},\n    {payload:1}]\nmsg.topic=\"arrayCommand\"\nreturn msg;","outputs":1,"noerr":0,"initialize":"","finalize":"","x":260,"y":2640,"wires":[["cc74d5dc.09b718"]]},{"id":"cc74d5dc.09b718","type":"function","z":"c791cbc0.84f648","name":"split array","func":"if (Array.isArray(msg.payload) && msg.topic === \"arrayCommand\"){\n    msg = [msg.payload]\n}\nreturn msg;","outputs":1,"noerr":0,"initialize":"","finalize":"","x":450,"y":2680,"wires":[["341d10d2.6707"]]},{"id":"341d10d2.6707","type":"function","z":"c791cbc0.84f648","name":"process","func":"\nreturn msg;","outputs":1,"noerr":0,"initialize":"","finalize":"","x":610,"y":2680,"wires":[["d3c8d390.e6ba38"]]},{"id":"65493f2f.6b808","type":"inject","z":"c791cbc0.84f648","name":"msg2","props":[{"p":"payload"}],"repeat":"","crontab":"","once":false,"onceDelay":0.1,"topic":"","payload":"2","payloadType":"num","x":110,"y":2700,"wires":[["cc74d5dc.09b718"]]},{"id":"d3c8d390.e6ba38","type":"debug","z":"c791cbc0.84f648","name":"","active":true,"tosidebar":true,"console":false,"tostatus":false,"complete":"true","targetType":"full","statusVal":"","statusType":"auto","x":750,"y":2680,"wires":[]}]
1 Like