2012-02-23 17 views
7

Sto cercando un modo per eseguire un processo PHP con un timeout. Attualmente sto semplicemente usando exec(), ma non fornisce un'opzione di timeout.exec() con timeout

Quello che ho provato anche è aprire il processo usando proc_open() e usando stream_set_timeout() sul tubo risultante, ma non ha funzionato.

Quindi, c'è un modo per eseguire un comando (un comando PHP per la precisione) con un timeout? (PS: Questo è per i casi in cui il limite max_execution_time non riesce, quindi nessun bisogno di suggerire che.)

(A proposito, ho anche bisogno di recuperare il codice di ritorno del processo.)

+0

timer di avvio php. mettere il processo in loop infinito, controllare il timer, timeout quando necessario. –

risposta

2

Si potrebbe fork() e quindi exec() in un processo e wait() non bloccante nell'altro. Inoltre, tieni traccia del timeout e kill() l'altro processo se non finisce in tempo.

+1

Ti stai riferendo alle funzioni pcntl_ qui? – NikiC

+0

@NikiC: Sì, penso che sia quello che vengono chiamati in PHP. – Florian

5

Ho trovato questo su php.net che penso possa fare ciò che si vuole

<?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); 
} 
?> 
+1

Questo sembra un approccio ragionevole, ma come si può ottenere il codice di ritorno in questo caso? – NikiC

15

Ho cercato un po 'su questo argomento e sono giunto alla conclusione che in alcuni casi (se si utilizza Linux) puoi usare il comando 'timeout'. E 'abbastanza flessibile

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

nel mio caso particolare sto cercando di eseguire indicizzatore sfinge da PHP, un po' script di migrazione dei dati quindi ho bisogno di reindicizzare i miei documenti sfinge

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

Poi ho intenzione di analizzare l'output e decidere di dargli un'altra prova, o lanciare un'eccezione e abbandonare il mio script.

+0

Ottimo lavoro :) –

1

(Disclaimer: Sono stato sorpreso di non trovare alcuna soluzione valida per questo, quindi ho sfogliato la documentazione di proc e l'ho trovato abbastanza semplice, ecco una semplice risposta proc, che usa le funzioni native in un modo che fornisce risultati coerenti È inoltre possibile catturare l'output per scopi di registrazione.)

La riga di funzioni proc è proc_terminate (process-handler), che combinata con proc_get_status (process-handler) ottenendo la chiave "in esecuzione", è possibile mentre il ciclo con sleep esegue una chiamata exec sincrona con un tempo scaduto.

Quindi, fondamentalmente:

$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 :(
+0

Ma il manuale dice proc_get_status e proc_terminate funziona con la risorsa restituita solo da proc_open, non popen? –

1

La soluzione timeout {$time} command non funziona correttamente quando viene chiamato da uno script PHP. Nel mio caso, con un comando ssh su un server sbagliato (la chiave rsa non trovata e il server richiede una password), il processo è ancora in vita dopo il timeout definito.

Tuttavia ho trovato una funzione che funziona bene qui:

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; 
} 
0

Sto affrontando lo stesso problema che ho provato tutte le risposte di cui sopra , ma Windows Server non funziona con nessuno di questi, forse è la mia stupidità.

La mia soluzione finale di lavoro per le finestre sta eseguendo un file batch,

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" 

il comando

exec("timeout.bat {$time} your_program.exe");