Node.status is not set on receiving message

We have a fairly simple node, executing an external program. Upon incomming message node should update its status and display "executing". However node status doesn't change. Only in the end node status is "All operations finished".

Sourcecode of testnode.js

"use strict";

const execFile = require('child_process').execFileSync;

let externalProg = __dirname + "/wait.sh";

module.exports = function (RED) {

    function testnode(config) {

        RED.nodes.createNode(this, config);

        var node = this;        

        node.on('input', function (msg, send, done) {

            // For maximum backwards compatibility, check that send exists.

            // If this node is installed in Node-RED 0.x, it will need to

            // fallback to using `node.send`

            send = send || function () { node.send.apply(node, arguments) }

            done = done || function () { if (arguments.length > 0) node.error.apply(node, arguments) }

            node.status({fill:"yellow",shape:"dot",text: "Executing ..."});

            

            try {

                let res = execFile(externalProg, { encoding: "utf8" }).trim();

                msg.payload=res;

            }

            catch (error) {

                console.log(error);

                done("Error executing external file. Execute permisson set?", msg);

                node.status({fill:"red",shape:"dot",text: "Error executing file ..."})

                return;

            }

            send(msg);

            // If an error is hit, report it to the runtime

            node.status({fill:"green",shape:"dot",text: "All operations finished."})

            if (done) {

                done();

            }

        });

    }

    RED.nodes.registerType("testnode", testnode);

}

If you want to try it, you can use package.json und testnode.html from Reading from serial. Works in sample-js file, but when running in node-red serial port gets closed

Script simulating an external program

#!/bin/bash
sleep 5s
echo "Done waiting"

Flow:

grafik

[{"id":"d24c6e9f.28db78","type":"inject","z":"b451655f.5db818","name":"","props":[{"p":"payload"},{"p":"topic","vt":"str"}],"repeat":"","crontab":"","once":false,"onceDelay":0.1,"topic":"","payload":"","payloadType":"date","x":400,"y":280,"wires":[["465016d8.ef75a"]]},{"id":"465016d8.ef75a","type":"testnode","z":"b451655f.5db818","name":"testnode","serialport":"/dev/serial0","x":640,"y":280,"wires":[["f96506af.bb3e48"]]},{"id":"f96506af.bb3e48","type":"debug","z":"b451655f.5db818","name":"","active":true,"tosidebar":true,"console":false,"tostatus":false,"complete":"false","statusVal":"","statusType":"auto","x":810,"y":280,"wires":[]}]

What am I doing wrong? Why is there no node status displaying "Executing..."?

PS: I know maximum execution time for a node is 15 seconds. The real script will take 10 seconds at max, so that won't be a problem.

The problem comes because you are blocking the entire node.js process by using execFileSync. That prevents the runtime from fully processing the node.status call and getting the event to the editor to display the new status.

Where did you get that from? There is no 'maximum execution time' - however you really must not write code that blocks the whole node.js process for long periods. Use the async version of the function to allow the rest of the runtime (and your other flows) to continue operating.

I don't understand why it prevents the node from fully processing. It calls node.status. So it should wait until node.status is finished and then continue executing the code in the following lines.

I've modified the example. Problem occurs even without execFileSync, but instead also with doing an active wait in a for loop. So how could it be modified that it works as expected? Input event listener should only finish when all code inside has finished.

New node:

"use strict";

module.exports = function (RED) {
    function testnode(config) {
        RED.nodes.createNode(this, config);
		var node = this;		

		node.on('input', function (msg, send, done) {
			// For maximum backwards compatibility, check that send exists.
			// If this node is installed in Node-RED 0.x, it will need to
			// fallback to using `node.send`
			send = send || function () { node.send.apply(node, arguments) }
			done = done || function () { if (arguments.length > 0) node.error.apply(node, arguments) }

			node.status({fill:"yellow",shape:"dot",text: "Executing ..."});
			
            console.log("Before loop");
			for(let i=0;i<399999999;)
			{
				if(i%10000000 == 0)
				{
					node.status({fill:"green",shape:"dot",text: "Connecting..."})
					console.log("Working");
				}
				
				i=i+1;
			}
			console.log("After loop");
            
			send(msg);
			// If an error is hit, report it to the runtime
			node.status({fill:"green",shape:"dot",text: "All operations finished."})


			if (done) {
				done();
			}
        });
    }
    RED.nodes.registerType("testnode", testnode);
}

Maximum execution time is from https://nodered.org/docs/creating-nodes/node-js

In 0.17 and later, the runtime will timeout the node if it takes longer than 15 seconds. An error will be logged and the runtime will continue to operate.

Yes, because you've swapped one blocking piece of code, for another.

It is a really bad design pattern to do fully blocking code like this in node.js. That goes against the entire programming model that Node.js relies on.

You should use asynchronous actions whenever you are doing work that can take any significant time - that allows the node.js event loop to continue processing other work in the "background".

You are reading the section of the docs related to closing the Function node - when the 'close' event is triggered, the node has a maximum of 15 seconds to complete its work. That is completely unrelated to handling messages

You write, that I'm blocking the whole node.js process.
If created a sample flow where a simple function node just simulates some heavy work and wants to output a yellow status when executed and green status when finished.
grafik

node.status({fill:"yellow",shape:"dot",text: "Executing ..."});
for(let i=0;i<399999999;)
			{
				if(i%10000000 == 0)
				{
					node.status({fill:"green",shape:"dot",text: "Connecting..."})
					console.log("Working");
				}
				
				i=i+1;
			}
node.status({fill:"green",shape:"dot",text: "All operations finished."})			
return msg;

Flow:

[{"id":"f2604c45.158b6","type":"inject","z":"b451655f.5db818","name":"","props":[{"p":"payload"},{"p":"topic","vt":"str"}],"repeat":"","crontab":"","once":false,"onceDelay":0.1,"topic":"","payload":"","payloadType":"date","x":900,"y":200,"wires":[["86ce0cda.9df9"]]},{"id":"86ce0cda.9df9","type":"function","z":"b451655f.5db818","name":"","func":"node.status({fill:\"yellow\",shape:\"dot\",text: \"Executing ...\"});\nfor(let i=0;i<399999999;)\n\t\t\t{\n\t\t\t\tif(i%10000000 == 0)\n\t\t\t\t{\n\t\t\t\t\tnode.status({fill:\"green\",shape:\"dot\",text: \"Connecting...\"})\n\t\t\t\t\tconsole.log(\"Working\");\n\t\t\t\t}\n\t\t\t\t\n\t\t\t\ti=i+1;\n\t\t\t}\nnode.status({fill:\"green\",shape:\"dot\",text: \"All operations finished.\"})\t\t\t\nreturn msg;","outputs":1,"noerr":0,"initialize":"","finalize":"","x":1100,"y":200,"wires":[["9d7bf051.1c7388"]]},{"id":"9d7bf051.1c7388","type":"debug","z":"b451655f.5db818","name":"","active":true,"tosidebar":true,"console":false,"tostatus":false,"complete":"false","statusVal":"","statusType":"auto","x":1280,"y":200,"wires":[]}]

Even this single function blocks whole node red process. So how would that function be rewritten not to block whole Node-RED Process?

I also have about > 200 Nodes in my complete homeautomation system. Timing is sometimes very important.
Suppose an action is checked every second via an inject node and thus 60 times per minute.
Due to heavy systemload for a few seconds these > 200 nodes need together more than 1s execution time. Than timer event will me missed and in this minute it will be executed less than 60 times. This is not good, because completly different code lead to missing this event.

Once approach would be to identify how you can slice the work up into smaller batches and use setImmediate to schedule the next batch - allowing the node.js event loop to make progress on any other events that need handling.


node.status({fill:"yellow",shape:"dot",text: "Executing ..."});

function processBatch(start) {
    for (let i=start;i<start+1000;i++) {
        // do something
        if(i%10000000 == 0)
        {
             node.status({fill:"green",shape:"dot",text: "Connecting..."})
             console.log("Working");
         }
    }
    if (start+1000 < 399999999) {
        setImmediate(function() {
            processBatch(start+1000);
        })
    } else {
       node.status({fill:"green",shape:"dot",text: "All operations finished."})			
       node.send(msg)
    }
}
processBatch(0);

return;

All the more reason not to write code the starves the node.js event loop from being able to do anything.

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