NodeJS bug in Linux when executing child_process.fork? - node.js

I cannot reliably get a forked child process to send back a message to the parent that exceeds 219262 bytes.
The issue is only on Linux. In Windows, it works as expected. And this issue seems have been introduced between Node versions 1.0.1 and 1.0.2 - works fine on Node versions prior to 1.0.1 but not after.
(the maxBuffer option is not relevent for child_process.fork, it only applies to child_process.exec and child_process.execFile)
Below is the failing sample. Executing "node parent" on the command line will fail to output the child's "messageToParent" if it exceeds 219262 bytes on Linux.
parent.js is:
var cp = require('child_process');
var child = cp.fork('./child', [], {});
console.log('>>>PARENT ---> SENDING MESSAGE TO CHILD');
child.send({});
child.on('message', function(msg) {
console.log('>>>PARENT ---> MESSAGE RECEIVED FROM CHILD = ' + JSON.stringify(msg));
});
child.on('error', function(err) {
console.log('>>>PARENT ---> ERROR FROM CHILD. err = '+ err);
});
child.on('exit', function(code, signal) {
console.log('>>>PARENT ---> EXIT FROM CHILD. code='+code+' signal = '+ signal);
});
child.on('close', function(code, signal) {
console.log('>>>PARENT ---> CLOSE FROM CHILD. code='+code+' signal = '+signal);
});
child.on('disconnect', function() {
console.log('>>>PARENT ---> DISCONNECT FROM CHILD');
});
child.js is
process.on('message', function(messageFromParent) {
console.log('>>>>>>CHILD ---> RECEIVED MESSAGE FROM PARENT');
var messageToParent = "It would be too long to post on stackoverflow, but if I make this string longer than 219262 bytes, it fails to return to the parent in Linux. There is no such issue in Windows";
var ret = process.send(messageToParent);
console.log('>>>>>>CHILD ---> SENDING MESSAGE TO PARENT process.send returned ' + ret);
process.exit(0);
});
process.on('uncaughtException', function(err) {
process.send({ output: {ERROR:err} });
process.exit(-1);
});

Posting an answer in case anyone else stumbles into this issue (https://github.com/nodejs/node/issues/36268)
The above child.js works perfectly in Node versions prior to 1.0.1 since child_process.fork() used to be synchronous. So "process.send(messageToParent)", followed by "process.exit(0)" will always return messageToParent to parent.js.
In later versions of Node, however, process.send() is async. Therefore, the child must exit via process.exit() within a process.send callback, else a race condition is created between V8 javascript thread and IPC pipe.
Also - in Windows, the default IPC pipe buffer is large enough that the message is always returned to parent prior to child exiting. This is not the case in Linux. This explains why the above code works in Windows even with later versions of Node where process.send() is async.

Related

child process doesn't exit

Following is my gulp task:
httpServer.on("close", (code, signal) => {
console.log(
`child process terminated due to receipt of signal ${signal}, code ${code}`
);
console.log("httpServer close");
resolve();
})
);
await new Promise((resolve) => setTimeout(resolve, 2e3));
httpServer.kill("SIGINT");
await httpServerClose;
console.log("here");
I got following output:
Available on:
http://127.0.0.1:5001
http://192.168.31.122:5001
http://172.21.96.1:5001
http://172.18.192.1:5001
Hit CTRL-C to stop the server
child process terminated due to receipt of signal SIGINT, code null
httpServer close
here
[13:32:57] Finished 'build:release' after 2.01 s
The problem is port 5001 is still in use.
I'm on windows 10, node v16.13.1
.kill() doesn't guarantees that the process will successfully terminate. You may check for the resulting boolean that kill() returns, but that only indicates wheter the signal was delivered or not.
childProcess.on('close', (code, signal) => {
console.log(
`child process terminated due to receipt of signal ${signal}`);
});
// Send SIGHUP to process.
const signalDelivered = childProcess.kill('SIGHUP');
console.log(signalDelivered)
For some reason, the childProcess is still running / crashed or the SIGINT was not handled correctly.
My suggestion is that you implement "Graceful shutdown".
Remember, if in your childProcess code you are handling process.on('SIGINT', ..),you need to close the server that is listening connections and then call process.exit(0).
const server = app.listen(5001)
process.on('SIGINT',function(){
server.close(err => {
//exit after server is not listening anymore
process.exit(err ? 1 : 0)
})
});

How programmably wait until the process will start the endpoint

I have created a small example application in node.js with unit tests and acceptance tests here
Both unit and acceptance tests are run inside mocha process. Acceptance tests start from forking the process and basically running the server on before() method. after() method stops the process and
before((initialized) => {
console.log('before script');
serverProcess = child_process.fork('server.js');
serverProcess.on('close', function (code) {
console.log('child process exited with code ' + code);
});
setTimeout(() => {
console.log('1s elapsed');
initialized();
}, 1000);
The code without any delays works on my local gitlab-runner, however on server it's not always the case, so I have added delay - wait for a while until the server will start.
Empirically I have found that 1s is enough and .5s is not.
However, I would like to know what should I do to make sure that the server is.
Are there any solutions to run server, execute the tests and shutdown the server that works on Linux, Windows, docker and outside of it?
There is a good help about how to communicate between fork processes.
The idea will be to send a message from the child saying to it's dad (I am ready!). Then the dad will continue is work.
Example :
before((initialized) => {
serverProcess = child_process.fork('server.js');
serverProcess.on('close', function(code) {
console.log('child process exited with code ' + code);
});
serverProcess.on('close', function(code) {
console.log('child process exited with code ' + code);
});
// We add a backup plan. If it takes too long to launch, throw
const timeout = setTimeout(() => {
initialized(new Error('tiemout');
}, 30000);
// Cait for the child to send a message to us
serverProcess.on('message', function(str) {
if (str === 'init done') {
clearTimeout(timeout);
// server.js got successfully initialized
initialized();
}
});
});
// To add inside of your server.js listen
if (process.send) {
process.send("init done");
}

Creating NodeJS child processes asynchronously

I am creating child processes in NodeJS in a function called "pythonGraphTools" in a for loop after generating some variables that need to be passed in. This for loop may run 50 times.
Then I am writing to the stdin of the spawned process. However, sometimes I am getting a "Error:EPIPE writing to closed socket" error for this line py.stdin.write(JSON.stringify(dotfilepath));
I suspect it is because the child process has not yet completed spawning and am attempting to write to it when it is not ready. I have seen asynchronous spawning such as that on https://nodejs.org/api/child_process.html#child_process_child_process_spawn_command_args_options but these only seem to have asynch events for the flow of data/messages from child to parent.
Any insight in how I might make sure that the child is fully spawned before I call py.stdin.write()
function pythonGraphTools(dotfilepath,allGraphsPerTrans,graphtools_color,graphtools_label,transHashArray){
var spawn = require('child_process').spawn,
py = spawn('python', ['python_module.py']);
//write file to disk temporarily.
console.log("dotfilepath is "+dotfilepath)
fs.writeFile(dotfilepath,allGraphsPerTrans, function(err){ //must create a file first //2nd param was res_str_dot_no_lbl
if(err){
console.log("there was an error writing to file" + err);
}
//now send this dot file path to the python module which will make the graph
console.log("now writing to python module!"+py.pid)
console.log("nodejs colorarray length for debuging "+ graphtools_color.length)
py.stdin.write(JSON.stringify(dotfilepath)); //sending data to the python process!
py.stdin.write("\n")
py.stdin.write(JSON.stringify(graphtools_color)); // sending colours
py.stdin.write("\n")
py.stdin.write(JSON.stringify(graphtools_label));//sending opcodes
py.stdin.write("\n");
py.stdin.write(JSON.stringify(transHashArray));//sending opcodes
py.stdin.write("\n");
py.stdin.end();
});
var dataString=""; //variable to store return from python module
py.stdout.on('data', function(data){ // listen for data coming back from python!
dataString += data.toString();
});
py.stdout.on('end', function(){ //pythons stdout has finished - now do stuff
console.log(dataString); // print out everything collected from python stdout
//now delete temp dot file (with all dot files in it)
fs.stat(dotfilepath, function (err, stats) { //check first if there is a dot file
console.log(stats);//here we got all information of file in stats variable
if (err) {
return console.error(err);
}
fs.unlink(dotfilepath,function(err){ //actually deleting comment this functiont to not delete
if(err) return console.log(err);
console.log('file deleted successfully');
});//end unlink
});//end file stat
py.stdout.end();
}); // on python 'finish'
py.on('exit', function (code, signal) { //which process? add pid ?
console.log('child process '+py.pid +' exited with ' +
`code ${code} and signal ${signal}`);
});
}

Prevent sending data to stdin if spawn fails

In my Node.js (v0.10.9) code I'm trying to detect 2 cases:
an external tool (dot) is installed - in that case I want to send some data to stdin of created process
the external tool is not installed - in that case I want to display warning and I don't want to send anything to process' stdin
My problem is that I don't know how to send data to child's stdin if and only if the process was spawned successfully (i.e. stdin is ready for writing).
Following code works fine if dot is installed, but otherwise it tries to send data to the child although the child wasn't spawned.
var childProcess = require('child_process');
var child = childProcess.spawn('dot');
child.on('error', function (err) {
console.error('Failed to start child process: ' + err.message);
});
child.stdin.on('error', function(err) {
console.error('Working with child.stdin failed: ' + err.message);
});
// I want to execute following lines only if child process was spawned correctly
child.stdin.write('data');
child.stdin.end();
I'd need something like this
child.on('successful_spawn', function () {
child.stdin.write('data');
child.stdin.end();
});
From the node.js docs: http://nodejs.org/api/child_process.html#child_process_child_process_spawn_command_args_options
Example of checking for failed exec:
var spawn = require('child_process').spawn,
child = spawn('bad_command');
child.stderr.setEncoding('utf8');
child.stderr.on('data', function (data) {
if (/^execvp\(\)/.test(data)) {
console.log('Failed to start child process.');
}
});
Have a look at core-worker:
https://www.npmjs.com/package/core-worker
This package makes it a lot easier to handle processes.
I think what you want to do is something like that (from the docs):
import { process } from "core-worker";
const simpleChat = process("node chat.js", "Chat ready");
setTimeout(() => simpleChat.kill(), 360000); // wait an hour and close the chat
simpleChat.ready(500)
.then(console.log.bind(console, "You are now able to send messages."))
.then(::simpleChat.death)
.then(console.log.bind(console, "Chat closed"))
.catch(() => /* handle err */);
So if the process is not started correctly, none of the .then statements are executed which is exactly what you want to do, right?

node.js child fork process encoding

Sending a special characters (like ß) to forked child process in node.js does not work. It seems that child process can not read it.
I can show it on the very simple example where I am sending one character ("ß") to forked process and back.
The parrent proces
var child = fork("render.js");
child.on('message', function (m) {
res.send(m);
});
//this does not work, works fine with normal 's'
child.send("ß");
setTimeout(function () {
child.kill();
res.send("Timeout error");
}, 5000);
And the child proces
process.on('message', function (m) {
process.send(m)
process.exit();
});
For completeness, I am hosting node in IIS.
That is a bug in node as mentioned here. Does not work on version 0.10.1. Updating node to latest 0.10.5 fixes it for me.

Resources