PHP进程执行超时

发布于 2024-10-22 07:35:00 字数 1905 浏览 2 评论 0原文

我有以下代码:

/**
 * Executes a program and waits for it to finish, taking pipes into account.
 * @param string $cmd Command line to execute, including any arguments.
 * @param string $input Data for standard input.
 * @param integer $timeout How much to wait from program in msecs (-1 to wait indefinitely).
 * @return array Array of "stdout", "stderr" and "return".
 */
function execute($cmd,$stdin=null,$timeout=-1){
    $proc=proc_open(
        $cmd,
        array(array('pipe','r'),array('pipe','w'),array('pipe','w')),
        $pipes=null
    );
    fwrite($pipes[0],$stdin);                  fclose($pipes[0]);
    $stdout=stream_get_contents($pipes[1]);    fclose($pipes[1]);
    $stderr=stream_get_contents($pipes[2]);    fclose($pipes[2]);
    $return=proc_close($proc);
    return array(
        'stdout' => $stdout,
        'stderr' => $stderr,
        'return' => $return
    );
}

它有两个“问题”。

  • 代码是同步的;它会冻结,直到目标进程关闭。
  • 到目前为止,如果不发出不同类型的命令(例如 Linux 上的 $cmd > /dev/null &start / B $cmd 在 Windows 上)

我根本不介意“冻结”。我只需要实现该超时。

注意:该解决方案的跨平台兼容非常重要。同样重要的是, $cmd 不必更改 - 我正在运行一些复杂的命令,恐怕可能会出现一些问题,但是,这取决于修复的类型 -我很高兴听到这些,只是我更喜欢不同的选择。

我找到了一些可能有帮助的资源:

I have the following code:

/**
 * Executes a program and waits for it to finish, taking pipes into account.
 * @param string $cmd Command line to execute, including any arguments.
 * @param string $input Data for standard input.
 * @param integer $timeout How much to wait from program in msecs (-1 to wait indefinitely).
 * @return array Array of "stdout", "stderr" and "return".
 */
function execute($cmd,$stdin=null,$timeout=-1){
    $proc=proc_open(
        $cmd,
        array(array('pipe','r'),array('pipe','w'),array('pipe','w')),
        $pipes=null
    );
    fwrite($pipes[0],$stdin);                  fclose($pipes[0]);
    $stdout=stream_get_contents($pipes[1]);    fclose($pipes[1]);
    $stderr=stream_get_contents($pipes[2]);    fclose($pipes[2]);
    $return=proc_close($proc);
    return array(
        'stdout' => $stdout,
        'stderr' => $stderr,
        'return' => $return
    );
}

It has two "problems".

  • The code is synchronous; it freezes until the target process closes.
  • So far, I've not been able to it from "freezing" without issuing a different kind of command (such as $cmd > /dev/null & on linux and start /B $cmd on windows)

I don't mind the "freeze", at all. I just need to implement that timeout.

Note: It is important that the solution is cross-platform compatible. It is also important that the $cmd doesn't have to change - I'm running some complex commands and I'm afraid there may be some issues, however, this depends on the type of fix - I'm happy to hear these out, just that I'd prefer a different alternative.

I've found some resources that may help:

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(3

浮生未歇 2024-10-29 07:35:00

代码中有一些错误。

这实际上是有效的:

function execute($cmd, $stdin = null, $timeout = -1)
{
    $proc=proc_open(
        $cmd,
        array(array('pipe','r'), array('pipe','w'), array('pipe','w')),
        $pipes
    );
    var_dump($pipes);
    if (isset($stdin))
    {
        fwrite($pipes[0],$stdin);
    }
    fclose($pipes[0]);

    stream_set_timeout($pipes[1], 0);
    stream_set_timeout($pipes[2], 0);

    $stdout = '';

    $start = microtime();

    while ($data = fread($pipes[1], 4096))
    {
        $meta = stream_get_meta_data($pipes[1]);
        if (microtime()-$start>$timeout) break;
        if ($meta['timed_out']) continue;
        $stdout .= $data;
    }

    $stdout .= stream_get_contents($pipes[1]);
    $stderr = stream_get_contents($pipes[2]);
    $return = proc_close($proc);

    return array(
        'stdout' => $stdout,
        'stderr' => $stderr,
        'return' => $return
    );
}

There is a few mistakes on the code.

That is actually working:

function execute($cmd, $stdin = null, $timeout = -1)
{
    $proc=proc_open(
        $cmd,
        array(array('pipe','r'), array('pipe','w'), array('pipe','w')),
        $pipes
    );
    var_dump($pipes);
    if (isset($stdin))
    {
        fwrite($pipes[0],$stdin);
    }
    fclose($pipes[0]);

    stream_set_timeout($pipes[1], 0);
    stream_set_timeout($pipes[2], 0);

    $stdout = '';

    $start = microtime();

    while ($data = fread($pipes[1], 4096))
    {
        $meta = stream_get_meta_data($pipes[1]);
        if (microtime()-$start>$timeout) break;
        if ($meta['timed_out']) continue;
        $stdout .= $data;
    }

    $stdout .= stream_get_contents($pipes[1]);
    $stderr = stream_get_contents($pipes[2]);
    $return = proc_close($proc);

    return array(
        'stdout' => $stdout,
        'stderr' => $stderr,
        'return' => $return
    );
}
萌吟 2024-10-29 07:35:00

您可以考虑使用 fread 来更精细地控制代码正在执行的操作,而不是 stream_get_contents。与 stream_set_timeout 相结合可能会为您提供所需的内容。

我把一些东西放在一起,作为我认为可能有效的演示 - 该代码完全未经测试,并且不提供任何保证,但可能会将您引向正确的方向。 ;)

function execute($cmd,$stdin=null,$timeout=-1){
    $proc=proc_open(
        $cmd,
        array(array('pipe','r'),array('pipe','w'),array('pipe','w')),
        $pipes=null
    );
    fwrite($pipes[0],$stdin);                  fclose($pipes[0]);

    stream_set_timeout($pipes[1], 0);
    stream_set_timeout($pipes[2], 0);

    $stdout = '';

    $start = microtime();

    while ($data = fread($pipes[1], 4096))
    {
        $meta = stream_get_meta_data($pipes[1]);
        if (microtime()-$start>$timeout) break;
        if ($meta['timed_out']) continue;
        $stdout .= $data;
    }

    $return = proc_close($proc);
    $stdout .= stream_get_contents($pipes[1]);
    $stderr = stream_get_contents($pipes[2]);

    return array(
        'stdout' => $stdout,
        'stderr' => $stderr,
        'return' => $return
    );
}

Rather than stream_get_contents, you could look at using fread to gain more finely grained control over what your code is doing. That combined with stream_set_timeout may give you what you're looking for.

I tossed something together as a demonstration of what I was thinking might work - this code is completely untested and comes with no guarantees, but might send you in the right direction. ;)

function execute($cmd,$stdin=null,$timeout=-1){
    $proc=proc_open(
        $cmd,
        array(array('pipe','r'),array('pipe','w'),array('pipe','w')),
        $pipes=null
    );
    fwrite($pipes[0],$stdin);                  fclose($pipes[0]);

    stream_set_timeout($pipes[1], 0);
    stream_set_timeout($pipes[2], 0);

    $stdout = '';

    $start = microtime();

    while ($data = fread($pipes[1], 4096))
    {
        $meta = stream_get_meta_data($pipes[1]);
        if (microtime()-$start>$timeout) break;
        if ($meta['timed_out']) continue;
        $stdout .= $data;
    }

    $return = proc_close($proc);
    $stdout .= stream_get_contents($pipes[1]);
    $stderr = stream_get_contents($pipes[2]);

    return array(
        'stdout' => $stdout,
        'stderr' => $stderr,
        'return' => $return
    );
}
你如我软肋 2024-10-29 07:35:00

这似乎对我有用:

public function toPDF() {
    $doc = $this->getDocument();

    $descriptor = [
        ['pipe','r'],
        ['pipe','w'],
        ['file','/dev/null','w'], // STDERR
    ];
    $proc = proc_open('/usr/local/project/scripts/dompdf_cli.php',$descriptor,$pipes,sys_get_temp_dir());
    fwrite($pipes[0],"$doc[paper]\n$doc[html]");
    fclose($pipes[0]);

    $timeout = 30;

    stream_set_blocking($pipes[1], false);

    $pdf = '';

    $now = microtime(true);

    try {
        do {
            $elapsed = microtime(true) - $now;

            if($elapsed > $timeout) {
                throw new \Exception("PDF generation timed out after $timeout seconds");
            }
            $data = fread($pipes[1], 4096);
            if($data === false) {
                throw new \Exception("Read failed");
            }
            if(strlen($data) === 0) {
                usleep(50);
                continue;
            }
            $pdf .= $data;
        } while(!feof($pipes[1]));

        fclose($pipes[1]);
        $ret = proc_close($proc);
    } catch(\Exception $ex) {
        fclose($pipes[1]);
        proc_terminate($proc); // proc_close tends to hang if the process is timing out
        throw $ex;
    } 


    if($ret !== 0) {
        throw new \Exception("dompdf_cli returned non-zero exit status: $ret");
    }

    // dump('returning pdf');
    return $pdf;
}

我不确定 stream_set_timeout 的目的是什么 - 只是设置每次读取超时,但如果你想限制总体时间,你只需将流设置为非阻塞模式,然后计算所需的时间。

This seems to be working for me:

public function toPDF() {
    $doc = $this->getDocument();

    $descriptor = [
        ['pipe','r'],
        ['pipe','w'],
        ['file','/dev/null','w'], // STDERR
    ];
    $proc = proc_open('/usr/local/project/scripts/dompdf_cli.php',$descriptor,$pipes,sys_get_temp_dir());
    fwrite($pipes[0],"$doc[paper]\n$doc[html]");
    fclose($pipes[0]);

    $timeout = 30;

    stream_set_blocking($pipes[1], false);

    $pdf = '';

    $now = microtime(true);

    try {
        do {
            $elapsed = microtime(true) - $now;

            if($elapsed > $timeout) {
                throw new \Exception("PDF generation timed out after $timeout seconds");
            }
            $data = fread($pipes[1], 4096);
            if($data === false) {
                throw new \Exception("Read failed");
            }
            if(strlen($data) === 0) {
                usleep(50);
                continue;
            }
            $pdf .= $data;
        } while(!feof($pipes[1]));

        fclose($pipes[1]);
        $ret = proc_close($proc);
    } catch(\Exception $ex) {
        fclose($pipes[1]);
        proc_terminate($proc); // proc_close tends to hang if the process is timing out
        throw $ex;
    } 


    if($ret !== 0) {
        throw new \Exception("dompdf_cli returned non-zero exit status: $ret");
    }

    // dump('returning pdf');
    return $pdf;
}

I'm not sure what the purpose of stream_set_timeout is -- that just sets the per-read timeout, but if you want to limit the overall time, you just have to set the stream to non-blocking mode and then time how long it takes.

~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文