默认情况下,由child_process.spawn()
创建的子进程与父进程具有相同的进程组,除非使用了{detached:true}
选项进行调用。
要点是,该脚本在不同的环境中会有不同的行为:
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');
});
在交互式 shell 中,默认情况下,从 Ctl-C 发送的 SIGINT 信号将发送到整个进程组,因此非分离子进程将收到 SIGINT 信号并退出:
you@bash $ node spawn-test.js
^Cjust ignore SIGINT
you@bash [another-terminal-window] $ ps aux | grep sleep
... sleep 102
...但是对 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
然而,pm2是另一个完全不同的东西。即使您尝试上述技术,它也会杀死整个进程树,包括您的分离进程,即使有一个很长的 --kill-timeout
参数:
you@bash $ pm2 start spawn-test.js --kill-timeout 3600
you@bash $ pm2 stop spawn-test
you@bash $ ps aux | grep sleep
you@bash $ pm2 start spawn-test.js --kill-timeout 3600
you@bash $ pm2 reload spawn-test
you@bash $ ps aux | grep sleep
这似乎是 pm2 中的一个 bug。
我曾经遇到类似的问题,通过使用 init 系统 (在我的情况下是 systemd) 而不是 pm2,可以更好地控制信号处理。
在 systemd 上,默认会向整个进程组发送信号,但您可以使用 KillMode=mixed
仅将信号发送给父进程,但如果子进程超时运行,则仍然 SIGKILL 子进程。
我的 systemd unit 文件如下:
[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