在PHP中对popen / fgets施加时间限制

问题描述:

I want impose a time limit to a process reading using fgets opened by popen in PHP.

I have the next code:

$handle = popen("tail -F -n 30 /tmp/pushlog.txt 2>&1", "r");
while(!feof($handle)) {
    $buffer = fgets($handle);
    echo "data: ".$buffer."
";
    @ob_flush();
    flush();
}
pclose($handle);

I tried without success:

set_time_limit(60);
ignore_user_abort(false);

The process is as follow:

  1. The browser send a GET request waiting for a Answer in HTML5 Server side events format.
  2. The request is received by AWS Load Balancer and is forwarded to EC2 instances.
  3. The answer is the last 30 lines of the file
  4. The browser receive it in 30 messages and the connection is persisted.
  5. If tail command sends a new line it is returned else fgets wait undefined time until new line is returned from tail command.
  6. AWS Load Balancer after 60 seconds of network inactivity (No new lines in 60 seconds) closes the connection to the browser. The connection to EC2 instance is not closed.
  7. The browser detect that the connection is closed and it opens a new connection, the process go back to step 1.

AS this steps describe, the connection between AWS Load Balancer and EC2 instance is never closed, after a few hours/days there is hundreds and hundreds of tail and httpd process running and the server start not answering.

Of course it appear to be a AWS Load Balancer bug, but I don't want start a process to gain the attention from Amazon and wait for a fix.

My temporary solution is do a sudo kill tail to kill the process before the server becomes unstable.

I think PHP doesn't stop the script because PHP is "blocked" waiting for fgets to finish.

I know that the time limit of AWS Load Balancer is editable, but I want keep in the default value, even a higher limit is not going to fix the problem.

I don't know if I need change the question to How to execute a process in linux with a time limit / timeout?.

PHP 5.5.22 / Apache 2.4 / Linux Kernel 3.14.35-28.38.amzn1.x86_64

我想在PHP中使用popen打开的fgets对进程读取施加时间限制。 p>

我有下一个代码: p>

  $ handle = popen(“tail -F -n 30 /tmp/pushlog.txt 2>& 1”,  “r”); 
while(!feof($ handle)){
 $ buffer = fgets($ handle); 
 echo“data:”。$ buffer。“
”; 
 @ob_flush();  
 flush(); 
} 
 
clclose($ handle); 
  code>  pre> 
 
 

我试过没有成功: p>

  set_time_limit(60); 
ignore_user_abort(false); 
  code>  pre> 
 
 

过程如下: p>

    浏览器发送GET请求,等待HTML5 Server side events格式的答案。 li>
  1. 请求由AWS Load Balancer收到,并且已转发给EC2实例。 li>
  2. 答案是文件的最后30行 li>
  3. 浏览器在30条消息中接收它并且连接是持久的。 li>
  4. 如果tail命令发送新行,则返回其他fgets等待未定义时间,直到从tail命令返回新行。 / li>
  5. 网络不活动60秒后,AWS Load Balancer(60秒内没有新行)关闭与浏览器的连接。 与EC2实例的连接未关闭。 li>
  6. 浏览器检测到连接已关闭并打开新连接,该过程将返回步骤1. li> ol >

    根据此步骤描述,AWS Load Balancer和EC2实例之间的连接永远不会关闭,几小时/几天后,有数百个尾部和httpd进程正在运行且服务器开始无法应答。 p>

    当然它似乎是一个AWS Load Balancer错误,但我不想启动一个流程来吸引亚马逊的注意并等待修复。 p>

    我的临时解决方案是在服务器变得不稳定之前执行sudo kill tail来终止进程。 p>

    我认为PHP不会停止脚本,因为PHP被“阻止” 等待fgets完成。 p>

    我知道AWS Load Balancer的时间限制是可编辑的,但我希望保持默认值,即使更高的限制也无法解决问题 。 p>

    我不知道是否需要将问题改为如何执行 Linux中的进程是否有时间限制/超时?。 p>

    PHP 5.5.22 / Apache 2.4 / Linux内核3.14.35-28.38.amzn1.x86_64 p> DIV>

Tested with PHP 5.5.20:

//Change configuration.
set_time_limit(0);
ignore_user_abort(true);

//Open pipe & set non-blocking mode.
$descriptors  = array(0 => array('file', '/dev/null', 'r'),
                      1 => array('pipe', 'w'),
                      2 => array('file', '/dev/null', 'w'));
$process      = proc_open('exec tail -F -n 30 /tmp/pushlog.txt 2>&1',
                                $descriptors, $pipes, NULL, NULL) or exit;
$stream       = $pipes[1];
stream_set_blocking($stream, 0);

//Call stream_select with a 10 second timeout.
$read = array($stream); $write = NULL; $except = NULL;
while (!feof($stream) && !connection_aborted()
        && stream_select($read, $write, $except, 10)) {

    //Print out all the lines we can.
    while (($buffer = fgets($stream)) !== FALSE) {
        echo 'data: ' . $buffer . "
";
        @ob_flush();
        flush();
    }

}

//Clean up.
fclose($stream);
$status = proc_get_status($process);
if ($status !== FALSE && $status['running'] === TRUE)
    proc_terminate($process);
proc_close($process);

Rather than using a process file pointer, I went with my "multitasking" approach. I use this code to spawn other "processes" Kind of a multitasking cheat.

I call a Script, hang.php, that just hangs for 90 seconds: sleep(90).

You may want to adjust the stream and stream_select timeouts.

Create stream(s)

header('Content-Type: text/plain; charset=utf-8');
$timeout = 20; 
$result = array(); 
$sockets = array(); 
$buffer_size = 8192;
$id = 0;
$stream = stream_socket_client("ispeedlink.com:80", $errno,$errstr, $timeout,
    STREAM_CLIENT_ASYNC_CONNECT|STREAM_CLIENT_CONNECT); 
if ($stream) {
  $sockets[$id++] = $stream;  // supports multiple sockets
  $http = "GET /testbed/hang.php HTTP/1.0
Host: ispeedlink.com

"; 
  fwrite($stream, $http);
} 
else { 
  echo "$id Failed
";
} 

Additional scripts can be run by adding the stream: $sockets[$id++] = $stream;


Below will put anything read in to the $result[$id] array.

Monitor the streams:

while (count($sockets)) {
  $read = $sockets; 
  stream_select($read, $write = NULL, $except = NULL, $timeout); 
  if (count($read)) {
    foreach ($read as $r) { 
      $id = array_search($r, $sockets); 
      $data = fread($r, $buffer_size); 
      if (strlen($data) == 0) { // either reads data or EOF
        echo "$id Closed: " . date('h:i:s') . "


";
        fclose($r); 
        unset($sockets[$id]);
      } 
      else {
        $result[$id] .= $data; 
      }
    }
  }
  else { 
    echo 'Timeout: ' . date('h:i:s') . "


";
    break;
  }
}
echo system('ps auxww');

.


When I want to kill a process I use system('ps auxww') to get the pid and kill it with system("kill $pid")

kill.php

header('Content-Type: text/plain; charset=utf-8');
//system('kill 220613');

echo system('ps auxww');