在PHP中处理respawn和信号处理

时间:2015-03-13 16:13:15

标签: 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 '.__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
  • 通过SIGTERMSIGCONTob_*注册处理程序。首先只记录一个消息,即进程已终止,而第二个将导致重新生成进程。它是通过/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(并且脚本确实退出)。现在,如果我改为SIGCONT,那么我看到(18是Idle [ppid=7171] [pid=8849] Restarted [ppid=7171] [pid=8849] Idle [ppid=1] [pid=8875] Idle [ppid=1] [pid=8875] 的数值):

SIGCONT

因此:首先,ppid=1也被正确处理,并且,通过下一个“空闲”消息判断,新生成的脚本实例运行良好。

更新#1 :我正在考虑使用init(因此,ppid=1全局流程)和孤立处理信号处理的东西,但事实并非如此。这是pcntl_signal(),它表明孤立(system())进程不是原因:当通过控制app启动worker时,它也使用ppid=1命令调用它 - 与工作者一样重生自己。但是,在控制app调用worker之后,它有SIGKILL并正确响应信号,而如果worker重生,新副本没有响应它们,strace除外。因此,只有当工作人员重生自己时才会出现问题。

更新#2 :我试图分析4发生的事情。现在,这里有两个街区。

  1. 当工人尚未重生时 - log part 。看一下 5SIGCONT 这一行,这是我向流程发送kill -18,因此system()的时间。然后它触发所有链:写入文件,8调用并退出当前进程。
  2. 当工人已经自行重生时 - strace output 。在这里,看看行 9SIGCONT - 它们在收到SIGCONT后出现。第一个:看起来过程仍然以某种方式接收信号,其次,它忽略了信号。没有执行任何操作,但系统通知了SIGCONT已发送的进程。为什么然后进程忽略它 - 是问题(因为,如果SIGKILL的用户处理程序的安装失败,那么它应该结束执行,而进程没有结束)。对于nanosleep({1, 0}, <unfinished ...> +++ killed by SIGKILL +++ ,已经重生的工人的输出如下:

    SIGTERM
  3. 这表明,该信号已被接收并做了它应该做的事情。

    问题

    随着流程的重生,它既不会对SIGCONT做出反应,也不会对SIGKILL做出反应。但是,仍然可以使用kill -9 PID结束它(因此,kill 8875确实结束了该过程)。例如,对于上述过程,kill -18 8875SIGTERM都不会执行任何操作(进程将忽略信号并继续记录消息)。

    但是,我不会说注册信号完全失败 - 因为它重新定义了至少ppid = 1(通常会导致终止,而在这种情况下会被忽略)。另外我怀疑{{1}}指出了一些错误的东西,但我现在不能肯定地说。

    另外,我尝试过任何其他类型的信号(事实上,信号代码是什么并不重要,结果总是一样)

    问题

    这种行为可能是什么原因?我正在重新制定流程的方式是正确的吗?如果没有,还有哪些其他选项可以让新生成的进程正确使用用户定义的信号处理程序?

2 个答案:

答案 0 :(得分:1)

解决方案:最终,strace有助于了解问题。具体如下:

nanosleep({1, 0}, {0, 294396497})       = ? ERESTART_RESTARTBLOCK (Interrupted by signal)
restart_syscall(<... resuming interrupted call ...>) = 0

因此,它显示已收到信号,但忽略。为了完全回答这个问题,我需要弄清楚,为什么进程添加了忽略列表的信号,但是用pcntl_sigprocmask()强行解锁它们就是这样做了:

pcntl_sigprocmask(SIG_UNBLOCK, [SIGTERM, SIGCONT]);

然后一切顺利,重生过程接收/处理信号。我尝试仅添加SIGCONT用于解锁,例如 - 然后它被正确处理,而SIGTERM被阻止,这指向该事物,这正是无法分派信号的原因。

分辨率:出于某种原因,当进程在安装了信号处理程序的情况下自行生成时,新实例会将这些信号屏蔽为忽略。揭开他们的力量有力地解决了这个问题,但是为什么信号会在新的实例中被掩盖 - 这是一个悬而未决的问题。

答案 1 :(得分:0)

这是因为你通过执行system(foo)生成子进程,然后继续当前进程的死亡。因此,该过程变为孤儿,其父亲变为PID 1(init)。

您可以使用pstree命令查看更改。

在:

init─┬─cron
(...)
     └─screen─┬─zsh───pstree
              ├─3*[zsh]
              ├─zsh───php
              └─zsh───vim

后:

init─┬─cron
(...)
     └─php

维基百科指出:

  

Orphan进程是僵尸进程的相反情况,因为它指的是父进程在其子进程之前终止的情况,在这种情况下,这些子进程被称为孤儿&#34;。 / p>      

与子进程终止时发生的异步子到父通知(通过SIGCHLD信号)不同,子进程在父进程完成时不会立即通知。相反,系统只是重新定义了'#34; parent-pid&#34;子进程中的字段数据是&#34;祖先&#34;系统中的每个其他进程,其pid通常具有值1(一),并且其名称传统上是&#34; init&#34;。据说,&#34; init&#39;采用&#39;系统上的每个孤儿进程&#34;。

根据您的情况,我建议两种选择:

  • 使用两个脚本:一个用于管理孩子,第二个用于&#34; worker&#34;,以实际执行该工作,
  • 或者,使用一个脚本,其中包括:外部部分将管理,内部部分,从外部分叉,将完成工作。