web-dev-qa-db-fra.com

Différence entre les événements de fermeture et de sortie de ChildProcess

Lors de la génération de processus enfants via spawn()/exec()/... dans Node.js, il existe un événement 'close' et un événement 'exit' sur les processus enfants.

Quelle est la différence entre ces deux et quand avez-vous besoin de quoi?

21
Narigo

Avant Node.js 0.7.7, il n'y avait qu'un événement "exit" sur les processus enfants (et aucun événement "close"). Cet événement serait déclenché à la fin du processus enfant et tous les flux (stdin, stdout, stdout) étaient fermés.

Dans le nœud 0.7.7 , l'événement "close" a été introduit ( voir commit ) . La documentation (permalien) indique actuellement:

L'événement 'close' est émis lorsque les flux stdio d'un processus enfant ont été fermés. Ceci est distinct de l'événement 'exit', car plusieurs processus peuvent partager les mêmes flux stdio.

Si vous créez un programme et ne faites rien de spécial avec stdio, l'événement "close" est déclenché après "exit". L’événement "close" peut être retardé si par ex. le flux stdout est redirigé vers un autre flux. Cela signifie donc que l'événement "close" peut être retardé (indéfiniment) après l'événement "exit".
Cela signifie-t-il que l'événement "close" est toujours déclenché après "exit"? Comme le montrent les exemples ci-dessous, la réponse est non.

Donc, si vous êtes seulement intéressé par la fin du processus (par exemple parce que le processus contient une ressource exclusive), écouter "exit" est suffisant . Si vous ne vous souciez pas du programme, et seulement de son entrée et/ou en sortie, utilisez l'événement "close". 

Expérience: détruire stdio avant de tuer un enfant

Expérimentalement (dans Node.js v7.2.0), j’ai trouvé que si les flux stdio ne sont pas utilisés par le processus enfant, l’événement "close" est uniquement déclenché après la fin du programme:

// 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);

Le programme ci-dessus générant des "sorties" de sommeil:

Closed all stdio
Going to kill
exited null SIGTERM
closed null SIGTERM

Quand je change les premières lignes en un programme qui ne sort que,

// The "yes" command continuously outputs lines with "y"
cp = require('child_process').spawn('yes');

... alors le résultat est:

Closed all stdio
exited 1 null
closed 1 null
Going to kill

De même, lorsque je change, je crée un programme qui ne lit que stdin,

// Keeps reading from stdin.
cp = require('child_process').spawn('node', ['-e', 'process.stdin.resume()']);

Ou quand je lis stdin et la sortie sur stdout,

// "cat" without arguments reads from stdin, and outputs to stdout
cp = require('child_process').spawn('cat');

Expérience: programmez un tuyau à un autre, tuez le premier programme

L'expérience précédente est assez artificielle. La prochaine expérience est un peu plus réaliste: vous dirigez un programme vers un autre et tuez le premier.

// 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);

Sortie:

Called kill()
exited null SIGTERM
closed null SIGTERM

De même lorsque le premier programme ne lit que des entrées et ne génère jamais:

// Keeps reading from stdin, never outputs.
cp = require('child_process').spawn('bash', ['-c', 'while read ; do : ; done']);

Lorsque le premier programme continue à générer des sorties sans attendre stdin, le comportement est différent, comme le montre l'expérience suivante.

Expérience: programme Pipe avec beaucoup de sorties sur un autre, tue le premier programme

// 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);

Le programme ci-dessus génère les éléments suivants, puis quitte:

Called kill()
exited null SIGTERM
Expecting "exit" to have fired, and not "close"
closed null SIGTERM
22
Rob W

Avez-vous regardé la documentation?

Selon this

L'événement 'close' est émis lorsque les flux stdio d'un processus enfant ont été fermés. Ceci est distinct de l'événement 'exit', car plusieurs processus peuvent partager les mêmes flux stdio.

L'événement 'exit' est émis à la fin du processus enfant. Si le processus s'est terminé, code est le code de sortie final du processus, sinon null. Si le processus s'est terminé en raison de la réception d'un signal, signal est le nom de chaîne du signal, sinon null. L'un des deux sera toujours non nul.

0
gibson

la version courte est, 'exit' émet quand l'enfant quitte mais les stdio sont pas encore fermé. 'close' émet quand l'enfant est sorti et ses stdios sont fermés.

En outre, ils partagent la même signature.

0
mh-cbon