Разница между событиями закрытия и выхода ChildProcess

При создании дочерних процессов через spawn()/exec()/... в Node.js в дочерних процессах возникает событие 'close' и 'exit'.

В чем разница между этими двумя и когда вам нужно что использовать?


person Narigo    schedule 30.05.2016    source источник


Ответы (3)


До Node.js 0.7.7 в дочерних процессах было только событие «выход» (и не было события «закрыть»). Это событие будет запущено, когда дочерний процесс завершится, и все потоки (stdin, stdout, stdout) будут закрыты.

В Node 0.7.7 введено событие закрытия (см. коммит). В документации (постоянная ссылка) в настоящее время говорится :

Событие close генерируется, когда потоки stdio дочернего процесса закрыты. Это отличается от события «выход», поскольку несколько процессов могут совместно использовать одни и те же потоки stdio.

Если вы просто запускаете программу и не делаете ничего особенного со stdio, событие «закрыть» срабатывает после «выхода». Событие «закрыть» может быть отложено, если, например. поток stdout перенаправляется в другой поток. Таким образом, это означает, что событие "закрытие" может быть отложено (на неопределенный срок) после события "выход".
Означает ли это, что событие "закрытие" всегда запускается после "выход"? Как показывают приведенные ниже примеры, ответ отрицательный.

Итак, если вас интересует только завершение процесса (например, потому что процесс содержит эксклюзивный ресурс), достаточно прослушивания «выхода». Если вас не интересует программа, а только ее ввод и/или вывод, используйте событие «закрыть».

Эксперимент: уничтожить stdio перед убийством ребенка

Экспериментально (в Node.js v7.2.0) я обнаружил, что если потоки stdio не используются дочерним процессом, то событие «закрыть» запускается только после выхода из программы:

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

Вышеупомянутая программа порождает «спящие» выходы:

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

Когда я изменяю первые строки на программу, которая только выводит,

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

... тогда вывод:

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

Точно так же, когда я изменяю порождение программы, которая читает только со стандартного ввода,

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

Или когда я читаю со стандартного ввода и вывожу на стандартный вывод,

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

Эксперимент: передать программу другой, убить первую программу

Предыдущий эксперимент довольно искусственный. Следующий эксперимент немного более реалистичен: вы передаете программу другой программе и уничтожаете первую.

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

Вывод:

Called kill()
exited null SIGTERM
closed null SIGTERM

Точно так же, когда первая программа только читает из ввода и никогда не выводит:

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

Когда первая программа продолжает вывод, не дожидаясь стандартного ввода, поведение отличается, как показывает следующий эксперимент.

Эксперимент: передать программу с большим количеством вывода другой программе, убить первую программу

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

Приведенная выше программа выводит следующее и затем завершает работу:

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

короткая версия: «выход» выдается, когда дочерний элемент выходит, но stdio еще не закрыт. 'close' срабатывает, когда дочерний элемент выходит из и его stdios закрывается.

Кроме того, они имеют одинаковую подпись.

person mh-cbon    schedule 30.05.2016
comment
эта информация, похоже, не согласуется с ответом @Gilad - person Alexander Mills; 12.10.2016

Вы документацию смотрели?

Согласно этому:

Событие close генерируется, когда потоки stdio дочернего процесса закрыты. Это отличается от события "выход", так как несколько процессов могут совместно использовать одни и те же потоки stdio.

Событие «выход» генерируется после завершения дочернего процесса. Если процесс завершился, код является окончательным кодом выхода процесса, в противном случае — null. Если процесс завершился из-за получения сигнала, signal является строковым именем сигнала, в противном случае null. Один из двух всегда будет ненулевым.

person gibson    schedule 30.05.2016
comment
Вы пропустили важную часть Note that when the 'exit' event is triggered, child process stdio streams might still be open. - person mh-cbon; 30.05.2016
comment
Проблема в том, что я не очень понимаю, что это значит. Будет ли называться «выход» и «закрытие»? Как процесс, который каким-то образом завершился, все еще может принимать/отправлять IO? Если «выход» вызывается перед «закрытием», как они оба могут иметь код выхода? - person Narigo; 30.05.2016