php.net |  support |  documentation |  report a bug |  advanced search |  search howto |  statistics |  random bug |  login
Request #33781 add "stream_set_timeout" support for pipes
Submitted: 2005-07-20 09:40 UTC Modified: 2011-04-08 21:48 UTC
From: vesely at tana dot it Assigned:
Status: Open Package: Streams related
PHP Version: * OS: *
Private report: No CVE-ID: None
Have you experienced this issue?
Rate the importance of this bug to you:

 [2005-07-20 09:40 UTC] vesely at tana dot it
Description:
------------
When using the Apache module it is not possible
to use pcntl_alarm (why?). OTOH stream_set_timeout
does not work on the "pipe" in descriptorspec.

Of course, if the program really doesn't know _if_
there is any data to read, it should use stream_select
and fgetc in a loop. Frequently enough, the program
knows what it is doing and wants to sleep on fgets
until data is available on the pipe. If anything goes
wrong here, the two processes may hang waiting for
each other. Meanwhile, the user angrily reloads the
page knocking out yet another Apache's child...

"NEEDED: a timeout for stdout pipe, otherwise a fgets on
$pipes[1] can lag forever...)" has been for years in
http://www.php.net/manual/en/function.proc-open.php#20866

The global script timeout is quite hard to manage from
an included function. stream_select and fgets is good
but is not bullet-proof. A request for a timer already
exists (bug #9676), hence I guess I should ask for some
"guarded-pipe" where the select-fgetc loop is coded in C
using a time limit that can be set via stream_set_timeout.
Is that cool?


Patches

Add a Patch

Pull Requests

Add a Pull Request

History

AllCommentsChangesGit/SVN commitsRelated reports
 [2005-07-20 14:54 UTC] wez@php.net
The simple solution is to NOT use fgets() when you're on a deadline, because of the greedy read semantic.

Use fread() instead, in conjunction with stream_select() (which is what is used internally to implement the timeout).

Here's an example from our test suite:

function system_with_timeout($commandline)
{
	$data = "";
	
	$proc = proc_open($commandline, array(
		0 => array('pipe', 'r'),
		1 => array('pipe', 'w'),
		2 => array('pipe', 'w')
		), $pipes, null, null, array("suppress_errors" => true));

	if (!$proc)
		return false;

	fclose($pipes[0]);

	while (true) {
		/* hide errors from interrupted syscalls */
		$r = $pipes;
		$w = null;
		$e = null;
		$n = @stream_select($r, $w, $e, 60);

		if ($n === 0) {
			/* timed out */
			$data .= "\n ** ERROR: process timed out **\n";
			proc_terminate($proc);
			return $data;
		} else if ($n > 0) {
			$line = fread($pipes[1], 8192);
			if (strlen($line) == 0) {
				/* EOF */
				break;
			}
			$data .= $line;
		}
	}
	$stat = proc_get_status($proc);
	if ($stat['signaled']) {
		$data .= "\nTermsig=".$stat['stopsig'];
	}
	$code = proc_close($proc);
	return $data;
}
 [2005-07-21 08:55 UTC] vesely at tana dot it
I have a couple of notes on that example. The function
attempts to fread 8Kbytes, whilst a positive response from
stream_select only guarantees that 1 byte can be read w/o
blocking. Further, 60 secs is really eons.

I'm using this:

   function guarded_fgets($pipes)
   {
      $line = "";
      $time = 2;
      $c = "c";
      while ($time >= 0 && ord($c) != 10)
      {
         $read = array($pipes[1]);
         while (!feof($read[0]) &&
            ($n = stream_select($read, $w = NULL, $e = NULL, $time)) !== FALSE &&
            $n > 0 &&
            strlen($c = fgetc($read[0])) > 0 &&
            ord($c) != 10)
               $line .= $c;
         --$time;
      }

      if (ord($c) != 10)
      {
         // error handling
      }

      return $line;
   }
 [2011-04-08 21:48 UTC] jani@php.net
-Package: Feature/Change Request +Package: Streams related -Operating System: Unix +Operating System: * -PHP Version: 4.4.0 +PHP Version: *
 
PHP Copyright © 2001-2017 The PHP Group
All rights reserved.
Last updated: Sun Nov 19 01:31:42 2017 UTC