PHP流程执行超时

PHP Process Execution Timeout

我有以下代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
/**
 * 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
    );
}

它有两个\\\\"问题\\\\"。

  • 代码是同步的;
  • 到目前为止,如果没有发出其他类型的命令(例如$cmd > /dev/null & on),则无法从\\\\" freeze \\\\"冻结它linux和Windows上的start /B $cmd)

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

注意:该解决方案必须跨平台兼容,这一点很重要。同样重要的是,不必更改$cmd-我正在运行一些复杂的命令,并且恐怕可能存在一些问题,但是,这取决于修复程序的类型-我\\'我很高兴听到这些消息,只是我希望使用其他方法。

我发现了一些资源可能会有所帮助:

  • 从PHP脚本运行perl文件但不等待Windows Server
  • PHP为系统调用的脚本设置超时,set_time_limit不起作用
  • http://www.shapeshifter.se/2008/08/04/asynchronous-用PHP执行背景/

代码上有一些错误。

这实际上是有效的:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
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
    );
}


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

我一起扔了一些东西,以证明我在想的是可行的-此代码未经测试,没有任何保证,但可能会发送给您在正确的方向。 ;)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
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
    );
}

这似乎对我有用:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
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]\
$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的目的是什么-只是设置了每次读取的超时时间,但是如果您想限制在整个时间上,您只需要将流设置为非阻塞模式,然后设置所需时间即可。