PHP中的进程再生和信号处理
细节 我在PHP中遇到了一个问题,当重新启动的进程没有处理信号时,而在重新启动之前,处理工作正常。我将我的代码缩小到最基本的:PHP中的进程再生和信号处理,php,unix,signals,pcntl,Php,Unix,Signals,Pcntl,细节 我在PHP中遇到了一个问题,当重新启动的进程没有处理信号时,而在重新启动之前,处理工作正常。我将我的代码缩小到最基本的: declare(ticks=1); register_shutdown_function(function() { if ($noRethrow = ob_get_contents()) { ob_end_clean(); exit; } system('/usr/bin/nohup /usr/bin/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
- 通过为
和SIGTERM
注册处理程序。第一个将只记录进程已终止的消息,而第二个将导致进程重新启动。它是通过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]
比如说,我确实杀了8849人:
Terminated [ppid=7171] [pid=8849]
因此,成功地处理了SIGTERM
(脚本确实存在)。现在,如果我改为执行kill-188849
,那么我看到(18是SIGCONT
的数值):
因此,首先,SIGCONT
也得到了正确的处理,从下一条“空闲”消息判断,新生成的脚本实例运行良好
更新#1:我在考虑使用ppid=1
(因此,init
全局进程)和孤立进程信号处理,但事实并非如此。这表明孤立(ppid=1
)进程不是原因:当worker通过控制应用程序启动时,它也会使用system()
命令调用它,就像worker重生一样。但是,在控制应用程序调用worker之后,它具有ppid=1
并正确响应信号,而如果worker自身重新启动,则新副本不会响应这些信号,除了SIGKILL
。所以,只有当工人自身重生时,问题才会出现
更新#2:我试图分析strace
发生了什么。现在,这里有两个街区
4
和5
行,这是我向进程发送SIGCONT
的时候,因此kill-18
。然后它触发所有的链:写入文件,system()
调用并退出当前进程8
行和第9
行-它们是在收到SIGCONT
后出现的。第一个:看起来进程仍在以某种方式接收信号,第二个,它忽略了信号。未执行任何操作,但系统通知流程已发送SIGCONT
。为什么流程会忽略它?这是一个问题(因为,如果为SIGCONT
安装用户处理程序失败,那么它应该在流程未结束时结束执行)。对于SIGKILL
,则已经重生的工人的输出如下所示:
nanosleep({1, 0}, <unfinished ...>
+++ killed by SIGKILL +++
nanosleep({1,0},
+++被西格基尔杀死+++
SIGTERM
,也不会对SIGCONT
做出反应。但是,仍然可以用SIGKILL
结束它(因此,kill-9pid
确实会结束进程)。例如,对于上面的进程,kill 8875
和kill-18 8875
都不会做任何事情(进程将忽略信号并继续记录消息)
然而,我不会说注册信号是完全失败的-因为它至少重新定义了SIGTERM
(通常会导致终止,而在本例中它被忽略)。我还怀疑ppid=1
指向了一些错误的东西,但我现在不能确定
此外,我还尝试了其他类型的信号(事实上,信号代码是什么并不重要,结果总是一样的)
问题
这种行为的原因可能是什么?我正在重新生成一个进程的方式是否正确?如果不正确,还有哪些其他选项可以允许新生成的进程正确使用用户定义的信号处理程序?这是因为您通过执行系统(foo)生成子进程,然后继续关闭当前进程。因此,该进程成为孤立进程,其父进程成为PID 1(init) 您可以使用
pstree
命令查看更改
之前:
init─┬─cron
(...)
└─screen─┬─zsh───pstree
├─3*[zsh]
├─zsh───php
└─zsh───vim
之后:
init─┬─cron
(...)
└─php
维基百科上说:
孤立进程与僵尸进程的情况相反,因为它指的是父进程在其子进程之前终止的情况,在这种情况下,这些子进程被称为“孤立”
与子进程终止(通过SIGCHLD信号)时发生的异步子进程到父进程的通知不同,子进程在其父进程结束时不会立即得到通知。相反,系统只是将子进程数据中的“父pid”字段重新定义为作为“祖先”的进程系统中所有其他进程的pid值通常为1(一),其名称传统上为“init”。因此,可以说“init”采用“系统上的每个孤立进程”
针对您的情况,我建议两种选择:
- 使用两个脚本:一个用于管理孩子,另一个“worker”用于实际执行任务
- 或者,使用一个脚本,这将包括两个:外部部分将管理,内部部分,从外部分叉,将完成任务
strace
帮助理解了问题。如下所示:
nanosleep({1, 0}, {0, 294396497}) = ? ERESTART_RESTARTBLOCK (Interrupted by signal)
restart_syscall(<... resuming interrupted call ...>) = 0
然后一切都会好起来,重生
nanosleep({1, 0}, {0, 294396497}) = ? ERESTART_RESTARTBLOCK (Interrupted by signal)
restart_syscall(<... resuming interrupted call ...>) = 0
pcntl_sigprocmask(SIG_UNBLOCK, [SIGTERM, SIGCONT]);