Difference between ChildProcess close, exit events - node.js

When spawning child processes via spawn()/exec()/... in Node.js, there is a 'close' and an 'exit' event on child processes.
What is the difference between those two and when do you need to use what?

Before Node.js 0.7.7, there was only an "exit" event on child processes (and no "close" event). This event would be fired when the child process has exited, and all streams (stdin, stdout, stdout) were closed.
In Node 0.7.7, the "close" event was introduced (see commit).
The documentation (permalink) currently says:
The 'close' event is emitted when the stdio streams of a child process have been closed. This is distinct from the 'exit' event, since multiple processes might share the same stdio streams.
If you just spawn a program and don't do anything special with stdio, the "close" event fires after "exit".
The "close" event can be delayed if e.g. the stdout stream is piped to another stream. So that means that the "close" event can be delayed (indefinitely) after the "exit" event.
Does this mean that the "close" event is always fired after "exit"? As the examples below show, the answer is no.
So, if you are only interested in the process termination (e.g. because the process holds an exclusive resource), listening for "exit" is sufficient.
If you don't care about the program, and only about its input and/or output, use the "close" event.
Experiment: destroy stdio before killing child
Experimentally (in Node.js v7.2.0), I found that if the stdio streams are not used by the child process, that then the "close" event is only fired after the program has exited:
// The "sleep" command takes no input and gives no output.
cp = require('child_process').spawn('sleep', ['100']);
cp.on('exit', console.log.bind(console, 'exited'));
cp.on('close', console.log.bind(console, 'closed'));
cp.stdin.end();
cp.stdout.destroy();
cp.stderr.destroy();
console.log('Closed all stdio');
setTimeout(function() {
console.log('Going to kill');
cp.kill();
}, 500);
The above program spawning "sleep" outputs:
Closed all stdio
Going to kill
exited null SIGTERM
closed null SIGTERM
When I change the first lines to a program that only outputs,
// The "yes" command continuously outputs lines with "y"
cp = require('child_process').spawn('yes');
... then the output is:
Closed all stdio
exited 1 null
closed 1 null
Going to kill
Similarly when I change spawn a program that only reads from stdin,
// Keeps reading from stdin.
cp = require('child_process').spawn('node', ['-e', 'process.stdin.resume()']);
Or when I read from stdin and output to stdout,
// "cat" without arguments reads from stdin, and outputs to stdout
cp = require('child_process').spawn('cat');
Experiment: Pipe program to another, kill first program
The previous experiment is quite artificial. The next experiment is a bit more realistic: You pipe a program to another and kill the first one.
// Reads from stdin, output the input to stdout, repeat.
cp = require('child_process').spawn('bash', ['-c', 'while read x ; do echo "$x" ; done']);
cp.on('exit', console.log.bind(console, 'exited'));
cp.on('close', console.log.bind(console, 'closed'));
cpNext = require('child_process').spawn('cat');
cp.stdout.pipe(cpNext.stdin);
setTimeout(function() {
// Let's assume that it has started. Now kill it.
cp.kill();
console.log('Called kill()');
}, 500);
Output:
Called kill()
exited null SIGTERM
closed null SIGTERM
Similarly when the first program only reads from input and never outputs:
// Keeps reading from stdin, never outputs.
cp = require('child_process').spawn('bash', ['-c', 'while read ; do : ; done']);
When the first program keeps outputting without waiting for stdin, the behavior is different though, as the next experiment shows.
Experiment: Pipe program with lots of output to another, kill first program
// Equivalent to "yes | cat".
cp = require('child_process').spawn('yes');
cp.on('exit', console.log.bind(console, 'exited'));
cp.on('close', console.log.bind(console, 'closed'));
cpNext = require('child_process').spawn('cat');
cp.stdout.pipe(cpNext.stdin);
setTimeout(function() {
// Let's assume that it has started. Now kill it.
cp.kill();
console.log('Called kill()');
setTimeout(function() {
console.log('Expecting "exit" to have fired, and not "close"');
// cpNext.kill();
// ^ Triggers 'error' event, errno ECONNRESET.
// ^ and does not fire the 'close' event!
// cp.stdout.unpipe(cpNext.stdin);
// ^ Does not appear to have any effect.
// ^ calling cpNext.kill() throws ECONNRESET.
// ^ and does not fire the 'close' event!
cp.stdout.destroy(); // <-- triggers 'close'
cpNext.stdin.destroy();
// ^ Without this, cpNext.kill() throws ECONNRESET.
cpNext.kill();
}, 500);
}, 500);
The above program outputs the following and then exits:
Called kill()
exited null SIGTERM
Expecting "exit" to have fired, and not "close"
closed null SIGTERM

the short version is, 'exit' emits when the child exits but the stdio are not yet closed.
'close' emits when the child has exited and its stdios are closed.
Besides that they share the same signature.

Related

end child gracefully when doing Ctrl-C in NodeJS

I have a architecture with one parent that spawns a child as follow :
this.process = child.spawn(this.cmd, this.args);
this.process.on('exit', (code: number, signal: string) => {
this.exitCode = code;
console.log(`exit code: ${code}`);
});
there is no particular option because i want to keep a link with the child. So, when i press Ctr-C to kill the parent, it catches SIGINT to (1) end the child, (2) exit properly. But SIGINT is also propagated to the child, so the parent cannot end it gracefully. So, is there a way to do so ? maybe by preventing SIGINT to be propagated to the child ? or telling the child to ignore the signal ?
i know that something is possible by adding option detached: true and stdio: 'ignore' when spawning, but i don't want to do that because if the parent dies, i end up with zombies process. Whereas keeping the links ensure that the child is killed if the parent dies unexpectedly. Finally, i want to avoid catching SIGINT in the child as i want to keep it dumb.
EDIT: the parent already have a process.on('SIGINT', () => { ... } and the child is in python.
You can catch exit codes like this:
process.on('SIGINT', () => {
// handle it yourself
})
You can propagate it to children like so:
process.on('SIGINT', () => {
this.child.kill('SIGINT')
})
The child can of course also choose to handle the signal, it is wise to not assume the child process will exit simply because you sent a single signal. You may need to set a timeout and send repeat or different signals to fully kill a process. You will also want to listen to the 'exit' message of the child process to know when its actually killed before continuing.
Documentation
Process Signal Events
Kill Sub-Process With Signal

How to execute code after a spawned, detached, child exits from NodeJS

My objective is to have some code execute after a detached, unreferenced, child process is spawned from a NodeJS app. Here is the code that I have:
var child_options = {
cwd : prj
, env : {
PATH: cmd_directory
}
, detatched : true
, stdio : 'ignore'
};
//Spawn a child process with myapp with the options and command line params
child = spawn('myapp', params_array, child_options, function(err, stdout, stderr){
if (err) {
console.log("\t\tProblem executing myapp =>\n\t\t" + err);
} else {
console.log("\t\tLaunched myapp successfully!")
}
});
//Handle the child processes exiting. Maybe send an email?
child.on('exit', function(data) {
fs.writeFile(path.resolve("/Users/me/Desktop/myapp-child.log"), "Finished with child process!");
});
//Let the child process run in its own session without parent
child.unref();
So the function inside the exit handler does not seem to get executed when the child process finishes. Is there any way at all to have code execute after the child process exits even when it's detached and when calling the .unref() method?
Note that if I change the 'stdio' key value in the child_options object from 'ignore' to 'inherit' then the exit handler does execute.
Any ideas?
UPDATE PART 1
So, I still can not figure this one out. I went back to the NodeJS docs on spawn, and noticed the example about spawning "long-running processes". In one example, they redirect the child process' output to files instead of just using 'ignore' for the 'stdio' option. So I changed the 'stdio' key within the child_options object as in the following, but alas I am still not able to execute the code within the 'close' or 'exit' event:
var out_log = fs.openSync(path.resolve(os.tmpdir(), "stdout.log"), 'a'),
err_log = fs.openSync(path.resolve(os.tmpdir(), "stderr.log"), 'a');
var child_options = {
cwd : prj
, env : {
PATH: cmd_directory
}
, detatched : true
, stdio : ['ignore', out_log, err_log]
};
So, the stdout.log file does get the stdout from the child process—so I know it gets redirected. However, the code in the close or exit event still does not execute. Then I thought I would be able to detect when the writing to the out_log file was finished, in which case I would be able to execute code at that point. However, I cannot figure out how to do that. Any suggestions?
You can add listener to 'close' event, e.g. replace 'exit' with 'close'. It worked on my side even with 'ignore' stdio. Also, input parameter in callback is exit code number or null.
According to nodejs documentation difference between exit and close events:
The 'close' event is emitted when the stdio streams of a child process
have been closed. This is distinct from the 'exit' event, since
multiple processes might share the same stdio streams.
Hope it helps.

Nodejs: Send Ctrl+C to a child process on Windows

Hi I am using child_process.spwan to start a child process running a python script on Windows. The script listens on SIGINT to gracefully exits itself. But Windows does not support signals and all node did was simulating. So child_process.kill('SIGINT') on Windows is actually killing the process unconditionally (no graceful exit, python's SIGTERM/SIGINT handler not called). Also writing a ctrl+c character to stdin does not work either.
When I look into Python APIs, I got the CTRL_BREAK_EVENT and CTRL_C_EVENT that can serve the need. I am wondering if node has equivalent platform-specific APIs like these?
Related posts but not working ones:
How to send control C node.js and child_processes
sending crtl+c to a node.js spawned childprocess using stdin.write()?
You can use IPC messages to signal to the child that its time to stop and gracefully terminate. The below approach uses process.on('message') to listen for messages from the parent in the child process & child_process.send() to send messages from the parent to the child.
The below code has a 1 minute timeout set to exit if the child hangs or is taking to long to finish.
py-script-wrapper.js
// Handle messages sent from the Parent
process.on('message', (msg) => {
if (msg.action === 'STOP') {
// Execute Graceful Termination code
process.exit(0); // Exit Process with no Errors
}
});
Parent Process
const cp = require('child_process');
const py = cp.fork('./py-script-wrapper.js');
// On 'SIGINT'
process.on('SIGINT', () => {
// Send a message to the python script
py.send({ action: 'STOP' });
// Now that the child process has gracefully terminated
// exit parent process without error
py.on('exit', (code, signal) => {
process.exit(0);
});
// If the child took too long to exit
// Kill the child, and exit with a failure code
setTimeout(60000, () => {
py.kill();
process.exit(1);
});
});
You could send a 'quit' command via stdin to the Pyhthon process, that worked for me. In Python you need to create a thread which reads from stdin using input, once this returns, you set an event flag. In your main application loop you regularly check whether the event has been set and exit the program.
Python application (script.py):
import threading
import sys
def quit_watch(event):
input("Type enter to quit")
event.set()
def main():
stop = threading.Event()
threading.Thread(target=quit_watch, args=[stop]).start()
while True:
# do work, regularly check if stop is set
if stop.wait(1):
print("Stopping application loop")
break
if __name__ == '__main__':
main()
sys.exit(0)
Node.js application:
child_process = require('child_process')
child = child_process.spawn('python.exe', ['script.py'])
// check if process is still running
assert(child.kill(0) == true)
// to terminate gracefully, write newline to stdin
child.stdin.write('\n')
// check if process terminated itself
assert(child.kill(0) == false)

Nodejs child process exit before stdio streams close

I've just been experimenting with child processes and noticed that the exit event fires before the close event - the following code throws an error because this._instance.stdin no longer exists (this._instance is already null).
'use strict';
const spawn = require('child_process').spawn;
class Foo {
constructor() {
this._instance = null;
}
bar() {
this._instance = spawn('ls', ['-l']);
this._instance.on('close', (code, signal) => {
this._instance.stdin.end();
});
this._instance.on('exit', (code, signal) => {
this._instance = null;
});
return this._instance;
}
}
var foo = new Foo();
console.log(foo.bar());
The documentation states:
"Note that when the 'exit' event is triggered, child process stdio streams might still be open."
I wondered how this happens, why do the streams still exists after the process has exited? And how do they get 'closed', is this part handled by the OS or does node do the closing of the left over stdio streams?
In practice I wouldn't necessarily set this._instance to null on exit as it doesn't seem like a nice thing to do and is obviously a bit premature.
The documentation of the close event sheds some light on why this might happen.
In short, the stdio can be used by other processes which did not exit yet.
I haven't looked into the code itself, but it would make sense that the OS handles the part of closing the stdio streams. Think about piping stdio into multiple processes (piping with tee might be a good example).
In the presented case, I would suspect that you don't even need to end() stdin, as the close event suggests that the stdin stream has already been closed.

Node: Read spawn stderr/stdout after quick exit event?

I'm using Node's spawn to create a new process. Sometimes this process will exit very quickly with an error code and a message on stderr. It appears that stderr is getting lost in this quick turnaround. I've tried this:
reader.stderr.on('data', function (buf) {
console.log('stderr message: ' + buf);
});
reader.on('exit', function (code, signal) {
console.log('Exit');
});
Output:
Exit
stderr message: ERROR: Missing required option for command.
I also tried reading it in the exit listener, but no luck:
reader.on('exit', function (code, signal) {
console.log('Exit');
console.log('stderr: ' + reader.stderr.read());
});
Output:
Exit
stderr: null
So, it appears the problem is that the stderr output is too slow, and is late after the exit event where I need that information. How can I fix this?
Taken from the child_process docs for exit:
Note that the child process stdio streams might still be open.
They then describe the close event:
This event is emitted when the stdio streams of a child process have all terminated. This is distinct from 'exit', since multiple processes might share the same stdio streams.
So it looks like you should be using close, not exit.

Resources