
Question:
I'm running a third party script by using a wrapper class I've written which calls shell_exec()
and pipes into a file I parse later using php code. I should mention that this is working, but I am trying to enhance the functionality, having encountered a use case I hadn't thought of.
How is it best to manage timeout on shell_exec()? I was thinking of wrapping it in a try() catch()
but I'm not sure how to best handle the time component.
I've been reading a few questions on here relating to shell_exec()
and exec()
and it seems that by passing output params to exec()
you can get a return, but that does rely on the script finishing with a return status. Plus in my mini test page, I can't seem to get it to return any output!
The other option I thought about was using a modal dialog, with an ajax style spinner whilst the script it working, and setting a manual timeout in javascript. Which then gave the user a model dialog message about it failing/timeout and ending.
Are there any accepted methods for this use case?
My mini test, consisted of the following,
public $e_return = array(); public $e_status = ''; // Paths are absolute from / public function execCheck($domain){ exec($this->ssl_check_path." -s ".$domain." -p 443 > ".$this->folder.$this->filename." 2>&1 &", &$this->e_return, &$this->e_status); } // Returns Array ( ) 0
Using this question as ref, Can't execute PHP script using PHP exec
Solution:1
I would suggest you look into using proc_open
. You can configure it to return a stream resource, manually keep a timer, and if the timer expires before the process completes, you can terminate it with proc_terminate
. If it does complete before the timer expires, then you can use proc_close
then stream_get_contents
to grab the data that would have otherwise been written to stdout.
Solution:2
I write some working piece of code for such task. Function returns exit code (0 - OK, >0 - error) and writes stdout, stderr to reference variables.
/*execute program and write all output to $out terminate program if it runs more than 30 seconds */ execute("program --option", null, $out, $out, 30); echo $out; function execute($cmd, $stdin=null, &$stdout, &$stderr, $timeout=false) { $pipes = array(); $process = proc_open( $cmd, array(array('pipe','r'),array('pipe','w'),array('pipe','w')), $pipes ); $start = time(); $stdout = ''; $stderr = ''; if(is_resource($process)) { stream_set_blocking($pipes[0], 0); stream_set_blocking($pipes[1], 0); stream_set_blocking($pipes[2], 0); fwrite($pipes[0], $stdin); fclose($pipes[0]); } while(is_resource($process)) { //echo "."; $stdout .= stream_get_contents($pipes[1]); $stderr .= stream_get_contents($pipes[2]); if($timeout !== false && time() - $start > $timeout) { proc_terminate($process, 9); return 1; } $status = proc_get_status($process); if(!$status['running']) { fclose($pipes[1]); fclose($pipes[2]); proc_close($process); return $status['exitcode']; } usleep(100000); } return 1; }
Solution:3
I tried with popen()
, but there is no way to terminate the proccess afterwards. Also, stream_get_contents()
blocks the stream even when using stream_set_blocking on Windows, so I had to use fread instead. Furthermore, proc_terminate doesn't work properly on Windows, so I had to use an alternative kill function.
I've came up with this, it should work both on Windows and Linux now:
function execute($command, $timeout = 5) { $handle = proc_open($command, [['pipe', 'r'], ['pipe', 'w'], ['pipe', 'w']], $pipe); $startTime = microtime(true); /* Read the command output and kill it if the proccess surpassed the timeout */ while(!feof($pipe[1])) { $read .= fread($pipe[1], 8192); if($startTime + $timeout < microtime(true)) break; } kill(proc_get_status($handle)['pid']); proc_close($handle); return $read; } /* The proc_terminate() function doesn't end proccess properly on Windows */ function kill($pid) { return strstr(PHP_OS, 'WIN') ? exec("taskkill /F /T /PID $pid") : exec("kill -9 $pid"); }
Note:If u also have question or solution just comment us below or mail us on toontricks1994@gmail.com
EmoticonEmoticon