我正在寻找一种在超时时运行PHP进程的方法。目前我只是使用exec()
,但它没有提供超时选项。
我还尝试过使用proc_open()
并在生成的管道上使用stream_set_timeout()
打开流程,但这也不起作用。
那么,有没有办法在超时的情况下运行命令(精确的PHP命令)? (PS:这适用于max_execution_time
限制失败的情况,因此无需建议。)
(顺便说一句,我还需要检索进程的返回码。)
答案 0 :(得分:23)
我对这个主题进行了一些搜索并得出结论,在某些情况下(如果你使用的是linux),你可以使用'timeout'命令。它非常灵活
Usage: timeout [OPTION] DURATION COMMAND [ARG]...
or: timeout [OPTION]
在我的特定情况下,我正在尝试从PHP运行sphinx索引器,有点迁移数据脚本,所以我需要重新索引我的sphinx文档
exec("timeout {$time} indexer --rotate --all", $output);
然后我将分析输出并决定再尝试一次,或者抛出异常并退出我的脚本。
答案 1 :(得分:5)
我在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 :(得分:2)
您可以在一个流程中fork()
然后exec()
,在另一个流程中wait()
无阻塞。如果没有及时完成,还要跟踪超时和kill()
其他进程。
答案 3 :(得分:2)
从PHP脚本调用时,timeout {$time} command
解决方案无法正常工作。在我的情况下,使用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;
}
答案 4 :(得分:1)
(免责声明:我很惊讶地发现没有好的解决方案,然后我浏览了proc文档并发现它很直接。所以这是一个简单的proc答案,它以一种提供一致结果的方式使用本机函数。您还可以捕获输出以进行日志记录。)
proc函数行有proc_terminate ( process-handler )
,结合proc_get_status ( process-handler )
获取“running”键,你可以在while 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 :(
答案 5 :(得分:1)
我遇到了同样的问题,我已经尝试了上面的所有答案,但是Windows服务器无法使用其中任何一个,也许这是我的愚蠢。
我最终的Windows解决方案是执行批处理文件,
<input type="text" name="name" autocomplete="off" />
timeout.bat
php命令
::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"
答案 6 :(得分:1)
在其他解决方案上的改进,我想到了:
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;
}