PHP Process Execution Timeout
I have the following code:
/**
* Executes a program and waits for it to finish, taking pipes into account.
* @param string $cmd Command line to execute, including any arguments.
* @param string $input Data for standard input.
* @param integer $timeout How much to wait from program in msecs (-1 to wait indefinitely).
* @return array Array of "stdout", "stderr" and "return".
*/
function execute($cmd,$stdin=null,$timeout=-1){
$proc=proc_open(
$cmd,
array(array('pipe','r'),array('pipe','w'),array('pipe','w')),
$pipes=null
);
fwrite($pipes[0],$stdin); fclose($pipes[0]);
$stdout=stream_get_contents($pipes[1]); fclose($pipes[1]);
$stderr=stream_get_contents($pipes[2]); fclose($pipes[2]);
$return=proc_close($proc);
return array(
'stdout' => $stdout,
'stderr' => $stderr,
'return' => $return
);
}
It has two "problems".
- The code is synchronous; it freezes until the target process closes.
- So far, I've not been able to it from "freezing" without issuing a different k开发者_StackOverflow中文版ind of command (such as
$cmd > /dev/null &
on linux andstart /B $cmd
on windows)
I don't mind the "freeze", at all. I just need to implement that timeout.
Note: It is important that the solution is cross-platform compatible. It is also important that the $cmd
doesn't have to change - I'm running some complex commands and I'm afraid there may be some issues, however, this depends on the type of fix - I'm happy to hear these out, just that I'd prefer a different alternative.
I've found some resources that may help:
- Run perl file from PHP script but not wait for output on Windows Server
- PHP set timeout for script with system call, set_time_limit not working
- http://www.shapeshifter.se/2008/08/04/asynchronous-background-execution-with-php/
There is a few mistakes on the code.
That is actually working:
function execute($cmd, $stdin = null, $timeout = -1)
{
$proc=proc_open(
$cmd,
array(array('pipe','r'), array('pipe','w'), array('pipe','w')),
$pipes
);
var_dump($pipes);
if (isset($stdin))
{
fwrite($pipes[0],$stdin);
}
fclose($pipes[0]);
stream_set_timeout($pipes[1], 0);
stream_set_timeout($pipes[2], 0);
$stdout = '';
$start = microtime();
while ($data = fread($pipes[1], 4096))
{
$meta = stream_get_meta_data($pipes[1]);
if (microtime()-$start>$timeout) break;
if ($meta['timed_out']) continue;
$stdout .= $data;
}
$stdout .= stream_get_contents($pipes[1]);
$stderr = stream_get_contents($pipes[2]);
$return = proc_close($proc);
return array(
'stdout' => $stdout,
'stderr' => $stderr,
'return' => $return
);
}
Rather than stream_get_contents
, you could look at using fread
to gain more finely grained control over what your code is doing. That combined with stream_set_timeout may give you what you're looking for.
I tossed something together as a demonstration of what I was thinking might work - this code is completely untested and comes with no guarantees, but might send you in the right direction. ;)
function execute($cmd,$stdin=null,$timeout=-1){
$proc=proc_open(
$cmd,
array(array('pipe','r'),array('pipe','w'),array('pipe','w')),
$pipes=null
);
fwrite($pipes[0],$stdin); fclose($pipes[0]);
stream_set_timeout($pipes[1], 0);
stream_set_timeout($pipes[2], 0);
$stdout = '';
$start = microtime();
while ($data = fread($pipes[1], 4096))
{
$meta = stream_get_meta_data($pipes[1]);
if (microtime()-$start>$timeout) break;
if ($meta['timed_out']) continue;
$stdout .= $data;
}
$return = proc_close($proc);
$stdout .= stream_get_contents($pipes[1]);
$stderr = stream_get_contents($pipes[2]);
return array(
'stdout' => $stdout,
'stderr' => $stderr,
'return' => $return
);
}
This seems to be working for me:
public function toPDF() {
$doc = $this->getDocument();
$descriptor = [
['pipe','r'],
['pipe','w'],
['file','/dev/null','w'], // STDERR
];
$proc = proc_open('/usr/local/project/scripts/dompdf_cli.php',$descriptor,$pipes,sys_get_temp_dir());
fwrite($pipes[0],"$doc[paper]\n$doc[html]");
fclose($pipes[0]);
$timeout = 30;
stream_set_blocking($pipes[1], false);
$pdf = '';
$now = microtime(true);
try {
do {
$elapsed = microtime(true) - $now;
if($elapsed > $timeout) {
throw new \Exception("PDF generation timed out after $timeout seconds");
}
$data = fread($pipes[1], 4096);
if($data === false) {
throw new \Exception("Read failed");
}
if(strlen($data) === 0) {
usleep(50);
continue;
}
$pdf .= $data;
} while(!feof($pipes[1]));
fclose($pipes[1]);
$ret = proc_close($proc);
} catch(\Exception $ex) {
fclose($pipes[1]);
proc_terminate($proc); // proc_close tends to hang if the process is timing out
throw $ex;
}
if($ret !== 0) {
throw new \Exception("dompdf_cli returned non-zero exit status: $ret");
}
// dump('returning pdf');
return $pdf;
}
I'm not sure what the purpose of stream_set_timeout
is -- that just sets the per-read timeout, but if you want to limit the overall time, you just have to set the stream to non-blocking mode and then time how long it takes.
精彩评论