exec () с таймаутом - PullRequest
       3

exec () с таймаутом

8 голосов
/ 23 февраля 2012

Я ищу способ запустить процесс PHP с таймаутом. В настоящее время я просто использую exec(), но он не предоставляет опцию тайм-аута.

Я также пытался открыть процесс, используя proc_open() и используя stream_set_timeout() на полученном канале, но это тоже не сработало.

Итак, есть ли способ запустить команду (точнее, команду PHP) с таймаутом? (PS: это для случаев, когда предел max_execution_time не работает, поэтому нет необходимости предлагать это.)

(Кстати, мне также нужно получить код возврата процесса.)

Ответы [ 7 ]

23 голосов
/ 10 апреля 2013

Я немного искал по этой теме и пришел к выводу, что в некоторых случаях (если вы используете Linux) вы можете использовать команду 'timeout'. Это довольно гибкий

Usage: timeout [OPTION] DURATION COMMAND [ARG]...
  or:  timeout [OPTION]

в моем конкретном случае я пытаюсь запустить индексатор sphinx из PHP, вроде сценария переноса данных, поэтому мне нужно переиндексировать документы sphinx

exec("timeout {$time} indexer --rotate --all", $output);

Затем я собираюсь проанализировать вывод и решить сделать еще одну попытку, или сгенерировать исключение и выйти из моего сценария.

5 голосов
/ 23 февраля 2012

Я нашел это на php.net, что я думаю, может делать то, что вы хотите

<?php 
function PsExecute($command, $timeout = 60, $sleep = 2) { 
    // First, execute the process, get the process ID 

    $pid = PsExec($command); 

    if( $pid === false ) 
        return false; 

    $cur = 0; 
    // Second, loop for $timeout seconds checking if process is running 
    while( $cur < $timeout ) { 
        sleep($sleep); 
        $cur += $sleep; 
        // If process is no longer running, return true; 

       echo "\n ---- $cur ------ \n"; 

        if( !PsExists($pid) ) 
            return true; // Process must have exited, success! 
    } 

    // If process is still running after timeout, kill the process and return false 
    PsKill($pid); 
    return false; 
} 

function PsExec($commandJob) { 

    $command = $commandJob.' > /dev/null 2>&1 & echo $!'; 
    exec($command ,$op); 
    $pid = (int)$op[0]; 

    if($pid!="") return $pid; 

    return false; 
} 

function PsExists($pid) { 

    exec("ps ax | grep $pid 2>&1", $output); 

    while( list(,$row) = each($output) ) { 

            $row_array = explode(" ", $row); 
            $check_pid = $row_array[0]; 

            if($pid == $check_pid) { 
                    return true; 
            } 

    } 

    return false; 
} 

function PsKill($pid) { 
    exec("kill -9 $pid", $output); 
} 
?>
2 голосов
/ 08 января 2014

Решение timeout {$time} command не работает должным образом, когда оно вызывается из скрипта PHP. В моем случае при использовании команды ssh на неправильном сервере (ключ rsa не найден, а сервер запрашивает пароль) процесс остается активным по истечении заданного времени ожидания.

Однако я нашел здесь функцию, которая прекрасно работает:

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;
}
2 голосов
/ 23 февраля 2012

Вы можете fork(), а затем exec() в одном процессе и wait() неблокировать в другом.Также следите за таймаутом и kill() другим процессом, если он не завершился вовремя.

1 голос
/ 05 ноября 2018

Улучшение других решений Я придумал это:

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;
}
1 голос
/ 06 января 2018

Я столкнулся с той же проблемой, что и я попробовал все ответы выше, но Windows Server не может работать ни с одним из них, возможно, это моя глупость.

Мое окончательное рабочее решение для Windows - выполнение пакетафайл,

timeout.bat

::param 1 is timeout seconds, param 2 is executable
echo "running %2 with timeout %1"
start %2
set time=0

:check
tasklist /FI "IMAGENAME eq %2" 2>NUL | find /I /N "%2">NUL
::time limit exceed
if "%time%"=="%1" goto kill
::program is running
if "%ERRORLEVEL%"=="0" ( ping 127.0.0.1 -n 2 >nul & set /a time=%time%+1 & goto check) else ( goto end)

:kill
echo "terminate"
taskkill /im %2 /f

:end
echo "end"

команда php

exec("timeout.bat {$time} your_program.exe");
1 голос
/ 07 мая 2013

(Отказ от ответственности: я был удивлен, не найдя хорошего решения для этого, затем я просмотрел документацию по proc и нашел ее довольно простой. Итак, вот простой ответ по proc, который использует собственные функции таким образом, чтобы обеспечить согласованные результаты.Вы также можете перехватить выходные данные для целей регистрации.)

В строке функций proc есть proc_terminate ( process-handler ), что в сочетании с proc_get_status ( process-handler ) получением клавиши "running" позволяет выполнять цикл с режимом сна, чтобы сделатьсинхронный вызов exec с тайм-аутом.

Итак, в основном:

$ps = popen('cmd');
$timeout = 5; //5 seconds
$starttime = time();
while(time() < $starttime + $timeout) //until the current time is greater than our start time, plus the timeout
{
    $status = proc_get_status($ps);
    if($status['running'])
        sleep(1);
    else
        return true; //command completed :)
}

proc_terminate($ps);
return false; //command timed out :(
...