php exec() 超时

声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow 原文地址: http://stackoverflow.com/questions/9419122/
Warning: these are provided under cc-by-sa 4.0 license. You are free to use/share it, But you must attribute it to the original authors (not me): StackOverFlow

提示:将鼠标放在中文语句上可以显示对应的英文。显示中英文
时间:2020-08-26 06:47:02  来源:igfitidea点击:

exec() with timeout

phptimeoutexec

提问by NikiC

I'm looking for a way to run a PHP process with a timeout. Currently I'm simply using exec(), but it does not provide a timeout option.

我正在寻找一种方法来运行一个超时的 PHP 进程。目前我只是使用exec(),但它不提供超时选项。

What I also tried is opening the process using proc_open()and using stream_set_timeout()on the resulting pipe, but that didn't work either.

我还尝试过在生成的管道上打开过程 usingproc_open()和 using stream_set_timeout(),但这也不起作用。

So, is there any way to run a command (a PHP command to be precise) with a timeout? (PS: This is for cases where the max_execution_timelimit fails, so no need to suggest that.)

那么,有没有办法在超时的情况下运行命令(准确地说是 PHP 命令)?(PS:这适用于max_execution_time限制失败的情况,因此无需建议。)

(By the way, I also need to retrieve the return code of the process.)

(顺便说一句,我还需要检索进程的返回码。)

回答by Davyd Dzhahaiev

I've searched a bit on this topic and came to conclusion that in some case (if you are using linux) you can use 'timeout' command. It's pretty flexible

我对这个主题进行了一些搜索,得出的结论是,在某些情况下(如果您使用的是 linux),您可以使用“超时”命令。它非常灵活

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

in my particular case I'm trying to run sphinx indexer from PHP, kinda migration data script so I need to reindex my sphinx documents

在我的特殊情况下,我试图从 PHP 运行 sphinx 索引器,有点迁移数据脚本,所以我需要重新索引我的 sphinx 文档

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

Then I'm going to analyze output and decide to give it one more try, or throw an exception and quit my script.

然后我将分析输出并决定再试一次,或者抛出异常并退出我的脚本。

回答by romo

I found this on php.net that I think can do what you want

我在 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); 
} 
?>

回答by Florian

You could fork()and then exec()in one process and wait()non-blocking in the other. Also keep track of the timeout and kill()the other process if it does not finish in time.

你可以在一个进程中fork()然后exec()在另一个进程中wait()非阻塞。kill()如果没有及时完成,还要跟踪超时和其他进程。

回答by Jason McCarrell

(Disclaimer: I was surprised to find no good solution for this, then I browsed the proc documentation and found it pretty straight forward. So here is a simple proc answer, that uses native functions in a way that provides consistent results. You can also still catch the output for logging purposes.)

(免责声明:我很惊讶地发现没有好的解决方案,然后我浏览了 proc 文档并发现它非常简单。所以这是一个简单的 proc 答案,它以提供一致结果的方式使用本机函数。您也可以仍然捕获输出以用于记录目的。)

The proc line of functions has proc_terminate ( process-handler ), which combined with proc_get_status ( process-handler )getting the "running" key, you can while loop with sleep to do a synchronous exec call with a timeout.

proc 函数行具有proc_terminate ( process-handler ),结合proc_get_status ( process-handler )获取“运行”键,您可以使用 sleep 进行 while 循环以执行超时的同步 exec 调用。

So basically:

所以基本上:

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

回答by juanra

The timeout {$time} commandsolution does not work properly when it's called from a PHP script. In my case, with a ssh command to a wrong server (rsa key not found, and the server ask for a password), the process still alive after the defined timeout.

timeout {$time} command从 PHP 脚本调用该解决方案时,该解决方案无法正常工作。在我的情况下,使用到错误服务器的 ssh 命令(未找到 rsa 密钥,并且服务器要求输入密码),该进程在定义的超时后仍然存在。

However I've found a function that works fine here:

但是我发现了一个在这里工作正常的函数:

http://blog.dubbelboer.com/2012/08/24/execute-with-timeout.html

http://blog.dubbelboer.com/2012/08/24/execute-with-timeout.html

C&P:

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

回答by Dipsy

I am facing the same problem that I have tried all the answers above, but windows server cant work with any of these, maybe it is my stupidity.

我面临着同样的问题,我已经尝试了上述所有答案,但 Windows Server 无法使用其中任何一个,也许这是我的愚蠢。

My final working solution for windows is executing a batch file,

我对 Windows 的最终工作解决方案是执行批处理文件,

timeout.bat

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"

the php command

php 命令

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

回答by dw1

Improving on other solutions I came up with this:

改进其他解决方案我想出了这个:

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