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
exec() with timeout
提问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_time
limit 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} command
solution 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;
}