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

时间:2012-04-02 12:28:32

标签: php sockets child-process

  

声明

     

我很清楚在这种情况下,对于套接字服务器来说,PHP可能不是最佳选择。请不要暗示   不同的语言/平台 - 相信我 - 我从所有人那里听到了它   方向。

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

我现在的主要问题是处理/处理这些死/僵尸子进程的集合并终止它们。我已经阅读(一遍又一遍)pcntl_fork()的相关PHP手册页,并意识到父流程负责清理其子女。当子进程执行exit(0)时,父进程从其子进程接收SIGNAL。我能够使用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]);
    }
  }
}

我确实看到了echo的,包括需要删除的相关且正确的子PID,但似乎

posix_kill($childPid, 9)

我理解为kill -9 $childPid的同义词返回TRUE,尽管事实上它并没有删除过程......

取自man pages of 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>

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

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

我对僵尸天启的计划是坚如磐石的 但即使sudo kill -9没有杀死僵尸子进程,我到底能做什么呢?


10天后更新

如果你仍然能够忍受我的谣言proceed at will,我在经过一些额外的研究后自己回答了这个问题。

4 个答案:

答案 0 :(得分:20)

  

我保证 最终会有一个解决方案:P

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

参加@sym's advice,并阅读文档以及文档评论的更多内容,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");

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

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); 
}

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

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

  

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

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

  

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

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


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

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

请滚筒

TL&amp; DR - 解决方案:

在2007年的php套接字文档和here this教程(搜索“良好的育儿”)的评论中提及stuporglue,可以简单地“忽略”信号传入来自子进程(SIGCHLD),将SIG_IGN传递给pcntl_signal()函数 -

pcntl_signal(SIGCHLD, SIG_IGN);

引用该链接的博文:

  

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

信不信由你 - 我包括了pcntl_signal()行,删除了所有其他处理程序以及与孩子们打交道的事情,并且它有效!没有剩下<defunct>个进程了!

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

答案 1 :(得分:4)

关注您的免责声明 - PHP在编写服务器方面并不比许多其他语言更好/更差。有些事情是不可能的(轻量级进程,异步I / O)但这些并不真正适用于分叉服务器。如果您正在使用OO代码,请确保您已启用循环引用检查垃圾收集器。

一旦子进程退出,它就变成了一个僵尸,直到父进程清理它。您的代码似乎在收到任何信号后向每个孩子发送KILL信号。它不会清理进程条目。它将终止尚未调用exit的进程。要正确获取子进程,您应该调用waitpid(另请参阅pcntl_wait手册页上的this example)。

答案 2 :(得分:2)

http://www.linuxsa.org.au/tips/zombies.html

  

僵尸是死的过程。你无法杀死死者。所有流程   最终死去,当他们这样做时,他们就变成了僵尸。他们消费   几乎没有资源,这是可以预料的,因为它们已经死了!   僵尸的原因是僵尸的父母(进程)可以   检索僵尸的退出状态和资源使用情况统计信息。该   父母通知操作系统它不再需要僵尸   通过使用其中一个wait()系统调用。

     

当一个进程死亡时,它的子进程都成为了进程的子进程   进程号1,这是init进程。 Init是“永远”   等待孩子们死去,这样他们就不会像僵尸一样。

     

如果你有僵尸进程,那就意味着那些僵尸还没有   等待他们的父母(看看ps -l显示的PPID)。您   有三个选择:修复父进程(让它等待);杀了   父母;或者和它一起生活。请记住,与它共处并不是那么难   因为僵尸在输出中只占用了一条额外的线   ps。

答案 3 :(得分:1)

我非常清楚你有多难找到解决僵尸进程问题的方法。我对可能拥有数百或数千个问题的关注是(正确或错误,因为我不知道这是否真的会成为一个问题)用完了inode,因为当发生这种情况时,所有地狱都会崩溃。

如果只有pcntl_fork()手册页链接到posix-setsid()我们很多人会发现解决方案在几年前就这么简单了。