Skip to content

Instantly share code, notes, and snippets.

@ChrisTG742
Last active March 15, 2025 14:27
Show Gist options
  • Select an option

  • Save ChrisTG742/c417e19d5034f1abcac6973acc6a5f28 to your computer and use it in GitHub Desktop.

Select an option

Save ChrisTG742/c417e19d5034f1abcac6973acc6a5f28 to your computer and use it in GitHub Desktop.
php exec() with timeout
<?php
/**
* Execute a command and return it's output. Either wait until the command exits or the timeout has expired.
* The syntax in compatible to the inbuilt exec().
*
* This is an improved version (parameter and output-compatible to exec()) of the original by Erik's Code here:
* https://blog.dubbelboer.com/2012/08/24/execute-with-timeout.html
*
* @param string $cmd Command to execute.
* @param string $output Returns command output
* @param string $exitcode Returns exitcode
* @param number $timeout Timeout in seconds (default 10s).
* @throws \Exception
*/
function exec_timeout($cmd, &$output, &$exitcode, $timeout = 10) {
// 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 non-blocking.
stream_set_blocking($pipes[1], 0);
// Set the stderr stream to non-blocking.
stream_set_blocking($pipes[2], 0);
// Turn the timeout into microseconds.
$timeout = $timeout * 1000000;
// Output buffer.
$output = '';
// 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 non-blocking.
$output .= 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;
// Wait 50ms before loop again
usleep(50000);
}
// 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);
// Close all pipe-streams.
fclose($pipes[0]);
fclose($pipes[1]);
fclose($pipes[2]);
// Convert multiline-string to array and filter-out empty lines
// to be compatible to exec()
$output = array_filter(explode("\n", $output));
// Close process and get exitcode
$exitcode = proc_close($process);
}
?>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment