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?
Spawn is useful when you want to make a continuous data transfer in binary/encoding format — e.g. transferring a 1 Gigabyte video, image, or log file. Fork is useful when you want to send individual messages — e.g. JSON or XML data messages.
spawn() or child_process. spawnSync() . child_process. exec() : spawns a shell and runs a command within that shell, passing the stdout and stderr to a callback function when complete.
Usually, Node. js allows single-threaded, non-blocking performance but running a single thread in a CPU cannot handle increasing workload hence the child_process module can be used to spawn child processes. The child processes communicate with each other using a built-in messaging system.
A child process is a process created by a parent process in operating system using a fork() system call. A child process may also be called a subprocess or a subtask. A child process is created as its parent process's copy and inherits most of its attributes.
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.
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');
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.
// 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
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With