Узел дочерние процессы: как перехватывают сигналы, как сигнал 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 8

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) могут просто сигнализировать ваш родительский процесс, так что дети остаются в живых:

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
Однако pm2-это совсем другой зверь. Даже если вы попробуете вышеперечисленные методы, это убьет все дерево процессов, включая ваш отделенный процесс, даже с длинным --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).