exec() с изчакване

Търся начин да стартирам PHP процес с изчакване. В момента просто използвам exec(), но той не предоставя опция за изчакване.

Това, което също опитах, е да отворя процеса с помощта на proc_open() и с помощта на stream_set_timeout() на резултантната тръба, но това също не проработи.

И така, има ли начин да се изпълни команда (по-точно PHP команда) с изчакване? (PS: Това е за случаите, когато ограничението max_execution_time е неуспешно, така че няма нужда да го предлагате.)

(Между другото, трябва също да извлека кода за връщане на процеса.)


person NikiC    schedule 23.02.2012    source източник
comment
стартирайте таймера. поставяне на процеса в безкраен цикъл, проверка на таймера, изчакване, когато е необходимо.   -  person    schedule 23.02.2012


Отговори (7)


Потърсих малко по тази тема и стигнах до извода, че в някои случаи (ако използвате linux) можете да използвате командата "timeout". Доста е гъвкав

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

в моя конкретен случай се опитвам да стартирам sphinx indexer от PHP, нещо като скрипт за данни за миграция, така че трябва да преиндексирам моите sphinx документи

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

След това ще анализирам изхода и ще реша да опитам още веднъж или да хвърля изключение и да напусна скрипта си.

person Davyd Dzhahaiev    schedule 10.04.2013
comment
Работи страхотно :) - person Sunny Patial; 14.07.2017

Намерих това на 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); 
} 
?>
person romo    schedule 23.02.2012
comment
Това изглежда като разумен подход, но как може да се получи кодът за връщане в този случай? - person NikiC; 24.02.2012

Можете да fork() и след това exec() в един процес и wait() без блокиране в другия. Също така следете времето за изчакване и kill() другия процес, ако не завърши навреме.

person Florian    schedule 23.02.2012
comment
Имате предвид функциите pcntl_ тук? - person NikiC; 23.02.2012
comment
@NikiC: Да, мисля, че така се казват в PHP. - person Florian; 23.02.2012

(Отказ от отговорност: Бях изненадан да не открия добро решение за това, след което прегледах документацията на proc и открих, че е доста ясен. Ето един прост отговор на proc, който използва естествени функции по начин, който осигурява последователни резултати. Можете също все още улавя изхода за целите на регистриране.)

Редът от функции на proc има proc_terminate ( process-handler ), което в комбинация с proc_get_status ( process-handler ) получаване на клавиша "работещ", можете да изпълнявате цикъл while със заспиване, за да извършите синхронно извикване на 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 :(
person Jason McCarrell    schedule 06.05.2013
comment
Но ръководството казва, че proc_get_status и proc_terminate работят само с ресурс, върнат от proc_open, а не от popen? - person Yuri Gor; 05.06.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;
}
person juanra    schedule 08.01.2014
comment
Открих, че има един проблем с този код. Въпреки че е вярно, че извикванията към stream_get_contents($pipes[1]) ще се върнат незабавно, тъй като тази тръба е била настроена на неблокираща, ПОСЛЕДНОТО извикване, което е stream_get_contents($pipes[2]), всъщност ще блокира, докато целият процес излезе, тъй като stderr тръбата никога не е била настроена на неблокираща. Това проваля цялата цел да можете да върнете контрола на повикващия след изтичане на времето. Решението е просто: Добавете stream_set_blocking($pipes[2], 0); след първото по-горе - person Danny; 14.01.2019
comment
@juanra: Нямам проблеми с използването на timeout {$time} command в нашия PHP скрипт. Какъв е проблемът, който видяхте? Може би нещо като неадекватно max_execution_time създаваше проблеми? - person rinogo; 25.02.2021

Изправен съм пред същия проблем, че опитах всички отговори по-горе, но 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");
person Dipsy    schedule 06.01.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;
}
person dw1    schedule 05.11.2018
comment
за малко по-голяма прецизност можете да замените time() с microtime(true) и също така да заспивате до някакъв по-малък интервал като usleep(100000) за 100ms сън - person Phil Glau; 06.11.2018