终止从套接字服务器分叉的僵尸子进程

Lix*_*Lix 17 php sockets child-process

放弃

我很清楚在这种情况下,对于套接字服务器来说,PHP可能不是最佳选择.请不要建议使用不同的语言/平台 - 相信我 - 我从各方面都听过.

Unix环境中工作并使用PHP 5.2.17,我的情况如下 - 我在PHP中构建了一个与Flash客户端通信的套接字服务器.我的第一个问题是每个传入的连接都阻塞了顺序连接,直到它完成处理.我用PHP 来解决这个问题pcntl_fork().我成功地能够产生许多子进程(在父节点中保存它们),这些进程负责向其他客户端广播消息,因此"释放"父进程并允许它继续处理下一个连接[s].

我现在的主要问题是处理/处理这些死/僵尸子进程的集合并终止它们.我已经阅读(一遍又一遍)pcntl_fork()的相关PHP手册页,并意识到父进程负责清理其子进程.当子进程执行时,父进程从其子进程接收SIGNAL exit(0).我能够使用pcntl_signal()函数"捕获"该信号来设置信号处理程序.

我的signal_handler看起来像这样:

declare(ticks = 1); 
function sig_handler($signo){ 
  global $forks; // this is an array that holds all the child PID's
  foreach($forks AS $key=>$childPid){
    echo "has my child {$childPid} gone away?".PHP_EOL;
    if (posix_kill($childPid, 9)){
      echo "Child {$childPid} has tragically died!".PHP_EOL;
      unset($forks[$key]);
    }
  }
}
Run Code Online (Sandbox Code Playgroud)

我确实看到两个echo都包括需要删除的相关且正确的子PID,但似乎是这样

posix_kill($childPid, 9)
Run Code Online (Sandbox Code Playgroud)

我理解为同义的kill -9 $childPid是返回TRUE,虽然它实际上并没有删除过程......

摘自以下手册posix_kill:

成功时返回TRUE,失败时返回FALSE.


我正在使用该ps命令监视子进程.它们在系统上显示如下:

web5      5296  5234  0 14:51 ?        00:00:00 [php] <defunct>
web5      5321  5234  0 14:51 ?        00:00:00 [php] <defunct>
web5      5466  5234  0 14:52 ?        00:00:00 [php] <defunct>
Run Code Online (Sandbox Code Playgroud)

正如您所看到的,所有这些进程都是具有PID的父进程的子进程 5234

我理解中遗漏了什么吗?我似乎已经设法让一切工作(并且确实如此)但我在系统上留下了无数的僵尸进程!

我对僵尸大灾难的计划是坚如磐石的 -
但即使sudo kill -9不杀死僵尸儿童过程,我还能做些什么呢?


10天后更新

经过一些额外的研究后,我自己已经回答了这个问题,如果你仍然可以随心所欲地继续我的随意.

Lix*_*Lix 20

我承诺,在最后的解决方案:P

好吧......所以我们在这里,10天后,我相信我已经解决了这个问题.我不想添加已经很长的帖子,所以我会在这个答案中包含一些我尝试过的东西.

根据@ sym的建议,并阅读更多文档文档的评论,pcntl_waitpid()描述指出:

如果pid请求的子节点在调用时已经退出(所谓的
"僵尸"进程),则该函数立即返回.孩子使用的任何系统资源
都被释放......

所以我设置我的pcntl_signal()处理程序 -

function sig_handler($signo){ 
    global $childProcesses;
    $pid = pcntl_waitpid(-1, $status, WNOHANG);
    echo "Sound the alarm! ";
    if ($pid != 0){
        if (posix_kill($pid, 9)){
            echo "Child {$pid} has tragically died!".PHP_EOL;
            unset($childProcesses[$pid]);
        }
    }
}
// These define the signal handling
// pcntl_signal(SIGTERM, "sig_handler");
// pcntl_signal(SIGHUP,  "sig_handler");
// pcntl_signal(SIGINT, "sig_handler");
pcntl_signal(SIGCHLD, "sig_handler");
Run Code Online (Sandbox Code Playgroud)

为了完成,我将包括我用于分支子进程的实际代码 -

function broadcastData($socketArray, $data){
        global $db,$childProcesses;
        $pid = pcntl_fork();
        if($pid == -1) {
                // Something went wrong (handle errors here)
                // Log error, email the admin, pull emergency stop, etc...
                echo "Could not fork()!!";
        } elseif($pid == 0) {
                // This part is only executed in the child
                foreach($socketArray AS $socket) {
                        // There's more happening here but the essence is this
                        socket_write($socket,$msg,strlen($msg));

                        // TODO : Consider additional forking here for each client. 
                }
                // This is where the signal is fired
                exit(0);
        }

        // If the child process did not exit above, then this code would be
        // executed by both parent and child. In my case, the child will 
        // never reach these commands. 
        $childProcesses[] = $pid;
        // The child process is now occupying the same database 
        // connection as its parent (in my case mysql). We have to
        // reinitialize the parent's DB connection in order to continue using it. 
        $db = dbEngine::factory(_dbEngine); 
}
Run Code Online (Sandbox Code Playgroud)

是的...这是对代码的1:1评论比例:P

所以这看起来很棒,我看到了回声:

发出警报!孩子12345悲惨地死了!

但是,当套接字服务器循环执行下一次迭代时,该socket_select()函数无法抛出此错误:

PHP警告:socket_select():无法选择[4]:系统调用中断...

服务器现在将进入植物人状态,完全忘记了他周围的世界,除了响应来自根终端的手动终止命令之外的任何请求.


我不打算讨论为什么会发生这种情况或者我之后做了什么来调试它...让我们说这是令人沮丧的一周...

很多咖啡,眼睛酸痛,10天后......

请滚筒

TL&DR - 解决方案:

提到这里的评论,从2007年的PHP插座文档和教程stuporglue(搜索"为人父母"),可以简单地"忽略"的信号从子进程(正在添加SIGCHLD通过传递)SIG_IGNpcntl_signal()功能-

pcntl_signal(SIGCHLD, SIG_IGN);
Run Code Online (Sandbox Code Playgroud)

引用该链接的博客帖子:

如果我们忽略SIGCHLD,子进程将在完成后自动获得.

信不信由你 - 我包括那条pcntl_signal()线,删除了所有其他处理程序和处理孩子的事情,它工作了!没有更多的<defunct>流程留下来了!

在我的情况下,我真的不感兴趣的是确切地知道一个子进程何时死亡,或者它是谁,我对它们根本不感兴趣 - 只是他们没有徘徊并崩溃我的整个服务器:P

  • 你刚刚拯救了我的一天!非常感谢!:) (2认同)