【问题标题】:proc_open leaves zombie processproc_open 离开僵尸进程
【发布时间】:2013-09-16 12:38:55
【问题描述】:

以下脚本监视/dev/shm/test 的新文件并实时输出有关它的信息。

问题是当用户关闭浏览器时,inotifywait 进程保持打开状态,依此类推。

有什么办法可以避免吗?

<?php
$descriptorspec = array(
  0 => array("pipe", "r"),  // stdin is a pipe that the child will read from
  1 => array("pipe", "w"),  // stdout is a pipe that the child will write to
  2 => array("pipe", "a") // stderr is a file to write to
);

$process = proc_open('inotifywait -mc -e create /dev/shm/test/', $descriptorspec, $pipes);

if (is_resource($process)) {

  header("Content-type: text/html;charset=utf-8;");
  ob_end_flush(); //ends the automatic ob started by PHP
  while ($s = fgets($pipes[1])) {
    print $s;
    flush();
  }
  fclose($pipes[1]);
  fclose($pipes[0]);
  fclose($pipes[2]);

  // It is important that you close any pipes before calling
  // proc_close in order to avoid a deadlock
  $return_value = proc_close($process);

  echo "command returned $return_value\n";
}
?>

【问题讨论】:

  • 你的代码包含一个无限循环。为什么你会期望它不会做那个无限循环?另外:您在这里使用的是哪个 SAPI?例如,如果您在 CLI 中启动该脚本并使用 CTRL+C 中止它,会发生什么情况? “子进程”被杀死了吗? proc_open 是打开子进程还是独立的? fgets 的超时时间是多少?我的意思是,如果 STDOUT 没有可读取的内容,你会怎么做 - 等待多长时间?
  • @hakre 我可以确认它在控制台模式下使用 CTRL+C 杀死了 inotifywait 进程。 fgets 没有超时,因为我尝试像 Jon 的回答一样检查 connection_aborted()proc_terminate() 但没有帮助,这很奇怪,因为据记录,PHP 只会在尝试输出某些内容后才知道连接被中止(我做)。另外,如果不检查这些函数,脚本应该在取消窗口加载时终止,你不觉得吗?
  • 你用的是什么php版本?

标签: php proc-open


【解决方案1】:

这是因为inotifywait 将等到文件/dev/shm/test/ 发生更改,然后在标准输出上输出有关标准错误的诊断信息和事件信息,并且fgets() 将等待直到它可以读取一行:$length - 1 个字节时读取结束(第二个参数)已被读取,或者换行符(包含在返回值中) 或 EOF(以先到者为准)。如果没有指定长度,它将一直从流中读取,直到到达行尾。

所以基本上,您应该使用stream_set_blocking($pipes[1], 0) 从子进程的标准输出管道非阻塞 模式读取数据,或者使用stream_select() 手动检查该管道上是否有数据。

另外,您需要使用ignore_user_abort(true) 忽略用户中止。

【讨论】:

    【解决方案2】:

    由于inotifywait 作为自己的进程运行,基本上永远不会结束,您需要向它发送 KILL 信号。如果您在 cli 上运行脚本,则 Ctrl+C 信号也会发送到 inotifywait 进程 - 但在网络服务器中运行时您没有。

    您在由register_shutdown_function__destruct 在类中调用的函数中发送信号。

    这个简单的 proc_open 包装器可能会有所帮助:

    class Proc
    {
        private $_process;
        private $_pipes;
    
        public function __construct($cmd, $descriptorspec, $cwd = null, $env = null)
        {
            $this->_process = proc_open($cmd, $descriptorspec, $this->_pipes, $cwd, $env);
            if (!is_resource($this->_process)) {
                throw new Exception("Command failed: $cmd");
            }
        }
    
        public function __destruct()
        {
            if ($this->isRunning()) {
                $this->terminate();
            }
        }
    
        public function pipe($nr)
        {
            return $this->_pipes[$nr];
        }
    
        public function terminate($signal = 15)
        {
            $ret = proc_terminate($this->_process, $signal);
            if (!$ret) {
                throw new Exception("terminate failed");
            }
        }
    
        public function close()
        {
            return proc_close($this->_process);
        }
    
        public function getStatus()
        {
            return proc_get_status($this->_process);
        }
    
        public function isRunning()
        {
            $st = $this->getStatus();
            return $st['running'];
        }
    }
    
    $descriptorspec = array(
        0 => array("pipe", "r"),  // stdin is a pipe that the child will read from
        1 => array("pipe", "w"),  // stdout is a pipe that the child will write to
        2 => array("pipe", "a") // stderr is a file to write to
    );
    $proc = new Proc('inotifywait -mc -e create /dev/shm/test/', $descriptorspec);
    
    header("Content-type: text/html;charset=utf-8;");
    ob_end_flush(); //ends the automatic ob started by PHP
    $pipe = $proc->pipe(1);
    while ($s = fgets($pipe)) {
        print $s;
        flush();
    }
    fclose($pipe);
    
    $return_value = proc->close($process);
    
    echo "command returned $return_value\n";
    

    或者你可以使用 Symfony 进程组件,它执行 exactly the same(加上其他有用的东西)

    【讨论】:

      【解决方案3】:

      您可以使用ignore_user_abort 指定脚本在用户关闭浏览器窗口时不应停止执行。这将解决一半的问题,因此您还需要使用connection_aborted 检查循环内的窗口是否已关闭,以确定何时需要有序地关闭所有内容:

      header("Content-type: text/html;charset=utf-8;");
      ignore_user_abort(true);
      ob_end_flush(); //ends the automatic ob started by PHP
      while ($s = fgets($pipes[1])) {
          print $s;
          flush();
          if (connection_aborted()) {
              proc_terminate($process);
              break;
          }
      }
      

      【讨论】:

      • 谢谢,它对你有用吗?我已经尝试了你告诉我的每一种组合,但我没有运气,这个过程仍然是开放的。我什至在取消浏览器以生成输出并让 PHP 知道连接已中止后创建了一个新文件,但没有运气。
      • @Jorge:实际上它对我也不起作用。不幸的是,我现在无法查看此问题,稍后会尝试返回。
      • 这很奇怪,可能是因为未知的(至少对我而言)语言问题,它卡在了$s = fgets($pipes[1]) 上。
      【解决方案4】:

      这有帮助吗?

      $proc_info = proc_get_status($process);
      pcntl_waitpid($proc_info['pid']);
      

      【讨论】:

        猜你喜欢
        • 2022-01-16
        • 2019-10-19
        • 2019-09-01
        • 1970-01-01
        • 2014-09-30
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        相关资源
        最近更新 更多