2012-02-23 20 views
7

Estoy buscando una forma de ejecutar un proceso de PHP con un tiempo de espera excedido. Actualmente solo estoy usando exec(), pero no proporciona una opción de tiempo de espera.exec() con tiempo de espera

Lo que también probé es abrir el proceso usando proc_open() y usando stream_set_timeout() en la tubería resultante, pero tampoco funcionó.

Entonces, ¿hay alguna manera de ejecutar un comando (un comando PHP para ser precisos) con un tiempo de espera? (PD: Esto es para casos en los que fracasa el límite max_execution_time, por lo que no hay necesidad de sugerir que.)

(Por cierto, yo también necesitan para recuperar el código de retorno del proceso.)

+0

temporizador de inicio php. ponga el proceso en un ciclo infinito, verificando el temporizador, tiempo de espera cuando sea necesario. –

Respuesta

2

Usted podría fork() y luego exec() en un proceso y wait() sin bloqueo en el otro. También realice un seguimiento del tiempo de espera y kill() el otro proceso si no termina a tiempo.

+1

¿Te refieres a las funciones pcntl_ aquí? – NikiC

+0

@NikiC: Sí, creo que así se llaman en PHP. – Florian

5

He encontrado esto en php.net que creo que puede hacer lo que quiera

<?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

Esto parece un enfoque razonable, pero ¿cómo se puede obtener el código de retorno en este caso? – NikiC

15

He buscado un poco en este tema y llegó a la conclusión de que en algunos casos (si está utilizando Linux) puedes usar el comando 'timeout'. Es bastante flexibles

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

en mi caso particular, estoy tratando de ejecutar indexador esfinge de PHP, un poco la escritura de datos de migración por lo que es necesario volver a indexar mis documentos esfinge

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

luego voy a analizo la salida y decido darle una oportunidad más, o lanzo una excepción y salgo de mi script.

+0

Funciona bien :) –

1

(Descargo de responsabilidad: Me sorprendió no encontrar una buena solución para esto, luego busqué la documentación de proc y encontré bastante sencillo. Aquí hay una respuesta de proceso simple, que usa funciones nativas de una manera que proporciona resultados consistentes . también puede hacer todavía coger la salida para propósitos de registro.)

la línea de proc de funciones tiene proc_terminate (process-handler), lo que combinado con proc_get_status (process-handler) conseguir la llave "en marcha", puede bucle while con el sueño de hacer una llamada exec sincrónica con una se acabó el tiempo.

Así que, básicamente:

$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

Pero el manual dice que proc_get_status y proc_terminate funcionan con el recurso devuelto por proc_open solamente, no popen? –

1

La solución timeout {$time} command no funciona correctamente cuando se llama desde un script PHP. En mi caso, con un comando ssh a un servidor incorrecto (no se encuentra la clave rsa, y el servidor solicita una contraseña), el proceso sigue vivo después del tiempo de espera definido.

Sin embargo he encontrado una función que funciona bien aquí:

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

estoy enfrentando el mismo problema que he probado todas las respuestas anteriores , pero Windows Server no puede funcionar con ninguno de estos, tal vez sea mi estupidez.

Mi solución de trabajo final para Windows está ejecutando un archivo por lotes,

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" 

el comando

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