【问题标题】:PHP Function TimeoutPHP 函数超时
【发布时间】:2016-04-28 02:30:51
【问题描述】:

我不是 PHP 专家。我有一个函数,它使用 EXEC 运行 WINRS,然后在远程服务器上运行命令。问题是这个函数被放置在一个循环中,该循环调用了几十次 getservicestatus 函数。有时,WINRS 命令可能会卡住或花费比预期更长的时间,从而导致 PHP 脚本超时并引发 500 错误。

我暂时降低了 PHP 中的设置超时值,并在 IIS 中创建了一个自定义的 500 页面,如果引用页面等于脚本名称,则重新加载页面(否则,抛出错误)。但这很混乱。显然,它并不适用于每次调用该函数,因为它是全局的。所以它只会避免页面在 HTTP 500 错误处停止。

我真正想做的是在函数本身上设置 5 秒的超时。我一直在搜索,但即使在 stackoverflow 上也找不到答案。是的,有类似的问题,但我找不到任何与我的职能相关的问题。也许在执行命令时有一种方法可以做到这一点,例如 exec() 的替代方法?我不知道。理想情况下,我希望函数在 5 秒后超时并将 $servicestate 返回为 0。

对代码进行了注释以解释我的意大利面混乱。很抱歉你必须看到它......

function getservicestatus($servername, $servicename, $username, $password)
{
//define start so that if an invalid result is reached the function can be restarted using goto.
start:

//Define command to use to get service status.
$command = 'winrs /r:' . $servername . ' /u:' . $username . ' /p:' . $password . ' sc query ' . $servicename . ' 2>&1';
exec($command, $output);

//Defines the server status as $servicestate which is stored in the fourth part of the command array.
//Then the string "STATE" and any number is stripped from $servicestate. This will leave only the status of the service (e.g. RUNNING or STOPPED).
$servicestate = $output[3];
$strremove = array('/STATE/','/:/','/[0-9]+/','/\s+/');
$servicestate = preg_replace($strremove, '', $servicestate);

//Define an invalid output. Sometimes the array is invalid. Catch this issue and restart the function for valid output. 
//Typically this can be caught when the string "SERVICE_NAME" is found in $output[3].
$badservicestate = "SERVICE_NAME" . $servicename;
if($servicestate == $badservicestate) {
    goto start;
}

//Service status (e.g. Running, Stopped Disabled) is returned as $servicestate.
return $servicestate;
}

【问题讨论】:

  • 我确定你不能有这样的start?您必须调用该函数。
  • 可能。但是像这样实施它实际上解决了我遇到的问题之一。基本上 winrs 命令的输出有时可能无效。我用 $badservicestate 和 goto start; 抓住了这一点。有用。但这并不意味着它是正确的。
  • 问题是exec() 将阻塞直到命令完成,除非你调用的外部命令有自己的超时方法,否则你将不得不适应@ 987654321@.

标签: php function timeout exec winrs


【解决方案1】:

最直接的解决方案是,因为您正在调用外部进程,并且您实际上需要在脚本中输出它的输出,所以将 exec 重写为 proc_open 和非阻塞 I/O:

function exec_timeout($cmd, $timeout, &$output = '') {
    $fdSpec = [
        0 => ['file', '/dev/null', 'r'], //nothing to send to child process
        1 => ['pipe', 'w'], //child process's stdout
        2 => ['file', '/dev/null', 'a'], //don't care about child process stderr
    ];
    $pipes = [];
    $proc = proc_open($cmd, $fdSpec, $pipes);
    stream_set_blocking($pipes[1], false);

    $stop = time() + $timeout;
    while(1) {
        $in = [$pipes[1]];
        $out = [];
        $err = [];
        stream_select($in, $out, $err, min(1, $stop - time()));

        if($in) {
            while(!feof($in[0])) {
                $output .= stream_get_contents($in[0]);
                break;
            }

            if(feof($in[0])) {
                break;
            }
        } else if($stop <= time()) {
            break;
        }
    }

    fclose($pipes[1]); //close process's stdout, since we're done with it 
    $status = proc_get_status($proc);
    if($status['running']) {
        proc_terminate($proc); //terminate, since close will block until the process exits itself

        return -1;
    } else {
        proc_close($proc);

        return $status['exitcode'];
    }
}

$returnValue = exec_timeout('YOUR COMMAND HERE', $timeout, $output);

这段代码:

  • 使用proc_open 打开一个子进程。我们只为孩子的stdout 指定管道,因为我们没有任何东西要发送给它,也不关心它的stderr 输出。如果这样做,则必须相应地调整以下代码。
  • stream_select() 上循环,这将阻塞一段时间,直至$timeout 集($stop - time())。
  • 如果有输入,它将var_dump()输入缓冲区的内容。这不会阻塞,因为我们在管道上有stream_set_blocking($pipe[1], false)。您可能希望将内容保存到变量中(追加而不是覆盖它),而不是打印出来。
  • 当我们读取了整个文件,或者我们已经超过了我们的超时,停止。
  • 通过关闭我们打开的进程进行清理。
  • 输出存储在传递引用字符串$output 中。返回进程的退出代码,或者在超时的情况下返回-1

【讨论】:

  • 我最终做了类似的事情,但这更好。谢谢!
猜你喜欢
  • 1970-01-01
  • 2012-05-22
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2014-08-09
  • 2012-06-18
  • 2015-01-13
相关资源
最近更新 更多