Last active
March 15, 2025 14:27
-
-
Save ChrisTG742/c417e19d5034f1abcac6973acc6a5f28 to your computer and use it in GitHub Desktop.
php exec() with timeout
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| <?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