Узел дочерние процессы: как перехватывают сигналы, как сигнал SIGINT
В моем узловом приложении я зацепляюсь за сигнал SIGINT
, чтобы изящно остановиться (используя pm2
, но это здесь не актуально).
Мое приложение также выполняет / порождает несколько дочерних процессов.
Я могу зацепиться за SIGINT
, чтобы перехватить его и выполнить изящную остановку, однако мои дочерние процессы пропускаются через тот же сигнал, и таким образом, мгновенно убиваются.
Как я могу перехватить сигнал SIGINT
на моих дочерних процессах?
Образец того, что я есть выполнение:
const child = child_process.spawn('sleep', ['10000000']);
console.log(`Child pid: ${child.pid}`);
child.on('exit', (code, signal) => { console.log('Exit', code, signal); });
process.on('SIGINT', () => {
console.log("Intercepting SIGINT");
});
2 ответа:
По умолчанию дочерние процессы, созданные
child_process.spawn()
, имеют ту же группу процессов, что и родительские, если только они не были вызваны с помощью{detached:true}
вариант .В результате этот сценарий будет вести себя по-разному в разных средах:
// spawn-test.js const { spawn } = require('child_process'); const one = spawn('sleep', ['101']); const two = spawn('sleep', ['102'], {detached: true}); two.unref(); process.on('SIGINT', function () { console.log('just ignore SIGINT'); });
В интерактивных оболочках SIGINT из Ctl-C по умолчанию передается всей группе, поэтому несвязанный ребенок получит SIGINT и выйдет:
you@bash $ node spawn-test.js ^Cjust ignore SIGINT you@bash [another-terminal-window] $ ps aux | grep sleep ... sleep 102 # note that sleep 101 is not running anymore # because it recieved the SIGINT from the Ctl-C
...но вызовы
kill(2)
могут просто сигнализировать ваш родительский процесс, так что дети остаются в живых:Однако pm2-это совсем другой зверь. Даже если вы попробуете вышеперечисленные методы, это убьет все дерево процессов, включая ваш отделенный процесс, даже с длиннымyou@bash $ node spawn-test.js & echo $? [2] 1234 you@bash [another-terminal-window] $ kill -SIGINT 1234 you@bash [another-terminal-window] $ ps aux | grep sleep ... sleep 101 ... sleep 102 # both are still running
--kill-timeout
:# Test pm2 stop you@bash $ pm2 start spawn-test.js --kill-timeout 3600 you@bash $ pm2 stop spawn-test you@bash $ ps aux | grep sleep # both are dead # Test pm3 reload you@bash $ pm2 start spawn-test.js --kill-timeout 3600 you@bash $ pm2 reload spawn-test you@bash $ ps aux | grep sleep # both have different PIDs and were therefore killed and restarted
Это похоже на ошибку в pm2.
Я обошел подобные проблемы, используя систему init (systemd в моем случае), а не pm2, так как это позволяет лучше контролировать обработку сигнала.
На systemd сигналы передаются всей группе посредством по умолчанию, но вы можете использовать
KillMode=mixed
, чтобы сигнал отправлялся только родительскому процессу,но все равно SIGKILL дочерние процессы, если они работают за пределами таймаута.Мои системные файлы выглядят следующим образом:
[Unit] Description=node server with long-running children example [Service] Type=simple Restart=always RestartSec=30 TimeoutStopSec=3600 KillMode=mixed ExecStart=/usr/local/bin/node /path/to/your/server.js [Install] WantedBy=multi-user.target
Обычно в C вы решаете эту проблему, игнорируя сигнал в дочерней группе (или порождая его в новой группе процессов, чтобы терминал, сгенерированный для группы процессов переднего плана, не достиг его).
От взгляда на https://nodejs.org/api/child_process.html#child_process_child_process_spawn_command_args_options, это не похоже на NodeJs предоставляет API для этого, однако, у него есть возможность для порождения дочернего процесса через оболочку, так что вы можете сделать следующее включите его и игнорируйте сигнал в оболочке, что приведет к тому, что его игнорируемый статус будет унаследован дочерними элементами оболочки.
const child_process = require('child_process') //const child = child_process.spawn('sleep', ['10000000']); const child = child_process.spawn("trap '' INT; sleep 10000000", [], {shell: true }); console.log(`Child pid: ${child.pid}`); child.on('exit', (code, signal) => { console.log('Exit', code, signal); }); process.on('SIGINT', () => { console.log("Intercepting SIGINT"); }); //emulate cat to keep the process alive process.stdin.pipe(process.stdout);
Теперь, когда вы нажимаете Ctrl-C, процесс узла обрабатывает его, и процесс сна продолжает жить. (Если вы не знакомы с другими генерируемыми терминалом сигналами, вы можете легко убить эту группу, нажав Ctrl-\ (отправляет SIGQUIT в группу), если вы не возражаете против coredump).