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 '

细节

我在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]);