细节
我在 PHP 中有一个问题,当重生的进程没有处理信号时,在重生之前,处理工作正常。我将代码缩小到非常基本的范围:
declare(ticks=1);
register_shutdown_function(function() {
if ($noRethrow = ob_get_contents()) {
ob_end_clean();
exit;
}
system('/usr/bin/nohup /usr/bin/php '.__FILE__. ' 1>/dev/null 2>/dev/null &');
});
function handler($signal)
{
switch ($signal) {
case SIGTERM:
file_put_contents(__FILE__.'.log', sprintf('Terminated [ppid=%s] [pid=%s]'.PHP_EOL, posix_getppid(), posix_getpid()), FILE_APPEND);
ob_start();
echo($signal);
exit;
case SIGCONT:
file_put_contents(__FILE__.'.log', sprintf('Restarted [ppid=%s] [pid=%s]'.PHP_EOL, posix_getppid(), posix_getpid()), FILE_APPEND);
exit;
}
}
pcntl_signal(SIGTERM, 'handler');
pcntl_signal(SIGCONT, 'handler');
while(1) {
if (time() % 5 == 0) {
file_put_contents(__FILE__.'.log', sprintf('Idle [ppid=%s] [pid=%s]'.PHP_EOL, posix_getppid(), posix_getpid()), FILE_APPEND);
}
sleep(1);
}
如您所见,它执行以下操作:
- 注册关闭函数,其中重新生成一个进程(因此,当父进程死亡时
nohup
忽略)SIGHUP
pcntl_signal()
通过forSIGTERM
和注册处理程序SIGCONT
。第一个将只记录一个进程已终止的消息,而第二个将导致进程重新启动。它是通过ob_*
函数实现的,所以要传递一个标志,应该在关闭函数中做什么——退出或重生。- 将脚本“活动”的一些信息记录到日志文件中。
怎么了
所以,我开始脚本:
/usr/bin/nohup /usr/bin/php script.php 1>/dev/null 2>/dev/null &
然后,在日志文件中,有如下条目:
Idle [ppid=7171] [pid=8849]
Idle [ppid=7171] [pid=8849]
比方说,然后我做kill 8849
:
Terminated [ppid=7171] [pid=8849]
因此,它是成功处理SIGTERM
(并且脚本确实退出)。现在,如果我改为这样做kill -18 8849
,那么我会看到(18 是 的数值SIGCONT
):
Idle [ppid=7171] [pid=8849]
Restarted [ppid=7171] [pid=8849]
Idle [ppid=1] [pid=8875]
Idle [ppid=1] [pid=8875]
因此:首先,SIGCONT
也得到了正确处理,并且根据下一个“空闲”消息判断,新生成的脚本实例运行良好。
更新#1:我在考虑ppid=1
(因此,init
全局进程)和孤儿进程信号处理的东西,但事实并非如此。这是日志部分,这表明孤儿(ppid=1
)进程不是原因:当通过控制应用程序启动worker时,它也使用system()
命令调用它 - 与worker自身重生的方式相同。但是,在控制应用调用 worker 后,它拥有ppid=1
并正确响应了信号,而如果 worker 自己重生,则新副本不会响应它们,除了SIGKILL
. 所以,只有当工人重生时才会出现问题。
更新 #2:我试图分析strace
. 现在,这里有两个块。
- 当工人尚未重生时 - strace 输出。看看行
4
和5
,这是我发送的时候SIGCONT
,因此kill -18
是一个进程。然后它触发所有链:写入文件,system()
调用和退出当前进程。 当 worker 已经自己重生时 - strace 输出。在这里,看看线路
8
和9
- 他们收到后出现SIGCONT
。首先:看起来进程仍在以某种方式接收信号,其次,它忽略了信号。未执行任何操作,但发送的系统通知了进程SIGCONT
。为什么然后进程忽略它 - 问题是(因为,如果安装用户处理程序SIGCONT
失败,那么它应该结束执行,而进程没有结束)。至于SIGKILL
,那么已经重生的工人的输出就像:nanosleep({1, 0}, <unfinished ...> +++ killed by SIGKILL +++
这表明,该信号已被接收并做了它应该做的事情。
问题
当进程重生时,它既不会对SIGTERM
也不会对做出反应SIGCONT
。但是,仍然可以结束它SIGKILL
(因此,kill -9 PID
确实结束了该过程)。例如,对于两者以上的进程,kill 8875
什么kill -18 8875
都不做(进程将忽略信号并继续记录消息)。
但是,我不会说注册信号完全失败 - 因为它至少重新定义SIGTERM
(这通常会导致终止,而在这种情况下它被忽略)。我也怀疑这ppid = 1
指向了一些错误的东西,但我现在不能肯定地说。
另外,我尝试了任何其他类型的信号(实际上,信号代码是什么并不重要,结果总是一样的)
问题
这种行为的原因可能是什么?我正在重生进程的方式正确吗?如果不是,还有哪些其他选项可以允许新生成的进程正确使用用户定义的信号处理程序?