Difference between ChildProcess close, exit events

node.jsEvent HandlingChild Process

node.js Problem Overview


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?

node.js Solutions


Solution 1 - node.js

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

Solution 2 - node.js

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.

Solution 3 - node.js

Did you look at the documentation?

According to this:

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.

The 'exit' event is emitted after the child process ends. If the process exited, code is the final exit code of the process, otherwise null. If the process terminated due to receipt of a signal, signal is the string name of the signal, otherwise null. One of the two will always be non-null.

Attributions

All content for this solution is sourced from the original question on Stackoverflow.

The content on this page is licensed under the Attribution-ShareAlike 4.0 International (CC BY-SA 4.0) license.

Content TypeOriginal AuthorOriginal Content on Stackoverflow
QuestionNarigoView Question on Stackoverflow
Solution 1 - node.jsRob WView Answer on Stackoverflow
Solution 2 - node.jsmh-cbonView Answer on Stackoverflow
Solution 3 - node.jsgibsonView Answer on Stackoverflow