shell_exec() timeout management & exec()

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

http://www.php.net/manual/en/function.exec.php

Here is Solutions:

We have many solutions to this problem, But we recommend you to use the first solution because it is tested & true solution that will 100% work for you.

Solution 1

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 2

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.

See http://www.php.net/manual/en/function.proc-open.php

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");
}

Solution 4

If you are under Linux (or WSL in Windows 10), then using the timeout command seems to be easiest way.
See this answer:
exec() with timeout

Note: Use and implement solution 1 because this method fully tested our system.
Thank you 🙂

All methods was sourced from stackoverflow.com or stackexchange.com, is licensed under cc by-sa 2.5, cc by-sa 3.0 and cc by-sa 4.0

Leave a Reply