Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Difference between ChildProcess close, exit events

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?

like image 650
Narigo Avatar asked May 30 '16 09:05

Narigo


People also ask

What is the difference between fork () and spawn () methods in node JS?

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.

What is child_process spawn?

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.

What is a child_process module in node JS?

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.

What is the use of child process?

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.


1 Answers

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 
like image 128
Rob W Avatar answered Sep 20 '22 14:09

Rob W