I'm looking for a way to run a PHP process with a timeout. Currently I'm simply using exec()
, but it does not provide a timeout option.
What I also tried is opening the process using proc_open()
and using stream_set_timeout()
on the resulting pipe, but that didn't work either.
So, is there any way to run a command (a PHP command to be precise) with a timeout? (PS: This is for cases where the max_execution_time
limit fails, so no need to suggest that.)
(By the way, I also need to retrieve the return code of the process.)
The timeout {$time} command
solution does not work properly when it's called from a PHP script. In my case, with a ssh command to a wrong server (rsa key not found, and the server ask for a password), the process still alive after the defined timeout.
However I've found a function that works fine here:
http://blog.dubbelboer.com/2012/08/24/execute-with-timeout.html
C&P:
/**
* Execute a command and return it's output. Either wait until the command exits or the timeout has expired.
*
* @param string $cmd Command to execute.
* @param number $timeout Timeout in seconds.
* @return string Output of the command.
* @throws \Exception
*/
function exec_timeout($cmd, $timeout) {
// File descriptors passed to the process.
$descriptors = array(
0 => array('pipe', 'r'), // stdin
1 => array('pipe', 'w'), // stdout
2 => array('pipe', 'w') // stderr
);
// Start the process.
$process = proc_open('exec ' . $cmd, $descriptors, $pipes);
if (!is_resource($process)) {
throw new \Exception('Could not execute process');
}
// Set the stdout stream to none-blocking.
stream_set_blocking($pipes[1], 0);
// Turn the timeout into microseconds.
$timeout = $timeout * 1000000;
// Output buffer.
$buffer = '';
// While we have time to wait.
while ($timeout > 0) {
$start = microtime(true);
// Wait until we have output or the timer expired.
$read = array($pipes[1]);
$other = array();
stream_select($read, $other, $other, 0, $timeout);
// Get the status of the process.
// Do this before we read from the stream,
// this way we can't lose the last bit of output if the process dies between these functions.
$status = proc_get_status($process);
// Read the contents from the buffer.
// This function will always return immediately as the stream is none-blocking.
$buffer .= stream_get_contents($pipes[1]);
if (!$status['running']) {
// Break from this loop if the process exited before the timeout.
break;
}
// Subtract the number of microseconds that we waited.
$timeout -= (microtime(true) - $start) * 1000000;
}
// Check if there were any errors.
$errors = stream_get_contents($pipes[2]);
if (!empty($errors)) {
throw new \Exception($errors);
}
// Kill the process in case the timeout expired and it's still running.
// If the process already exited this won't do anything.
proc_terminate($process, 9);
// Close all streams.
fclose($pipes[0]);
fclose($pipes[1]);
fclose($pipes[2]);
proc_close($process);
return $buffer;
}
Improving on other solutions I came up with this:
function exec_timeout($cmd,$timeout=60){
$start=time();
$outfile=uniqid('/tmp/out',1);
$pid=trim(shell_exec("$cmd >$outfile 2>&1 & echo $!"));
if(empty($pid)) return false;
while(1){
if(time()-$start>$timeout){
exec("kill -9 $pid",$null);
break;
}
$exists=trim(shell_exec("ps -p $pid -o pid="));
if(empty($exists)) break;
sleep(1);
}
$output=file_get_contents($outfile);
unlink($outfile);
return $output;
}
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With