SOFTELメモ Developer's blog

会社概要 ブログ 調査依頼 採用情報 ...
技術者募集中

【php】proc_open()からのstream_get_contents()で処理が固まる場合

問題

あるコマンドを、phpで、proc_open()して、stream_get_contents()しようとしてるのですが、
コマンドの処理は終わった様子なのに、phpがいつまでも終わりません。

何が起きてるんでしょう。

php-logo

答え

phpで標準出力も標準エラーも全部受け取るようにしていると、
標準出力や標準エラー出力のバッファがいっぱいになって固まることがあるらしいです。

対策1 エラー出力をそもそも出さない

実行しようとしているコマンドがエラー出力をやたらとおこなう場合、コマンドのオプションでエラー出力を抑制できるなら、抑制するとよい。

–verbose とか –quiet など指定できる場合がある(コマンドによる)。

command --quiet

対策2 エラー出力をファイルに逃がす

以下のようにすると固まるので、

$proc = proc_open($cmd, array(
            0 => array('pipe', 'r'),
            1 => array('pipe', 'w'),
            2 => array('pipe', 'w')
    ), $pipes);

以下のように、ため込まずにファイルに随時出力させると回避できた。

$proc = proc_open($cmd, array(
            0 => array('pipe', 'r'),
            1 => array('pipe', 'w'),
            2 => array('file', '/tmp/error-xxxx.txt', 'a')
    ), $pipes);

対策3 エラー出力を/dev/null に捨てる

捨てる。

$proc = proc_open($cmd, array(
            0 => array('pipe', 'r'),
            1 => array('pipe', 'w'),
            2 => array('file', '/dev/null', 'a')
    ), $pipes);

対策4 エラー出力をそもそも拾わない

2は捨てて、以下だけにする。

$proc = proc_open($cmd, array(
            0 => array('pipe', 'r'),
            1 => array('pipe', 'w')
    ), $pipes);

この場合、エラー出力をphpが受け取らないので、そのままエラー出力がエラー出力として出てくる(端末とかに)。

対策5 エラー出力をちゃんと読みながらちゃんと処理する

エラーも読む必要がある場合ならば、読まねばなるまい。
しかし、バッファがいっぱいになるとまずいようなので、上手に読む。

$proc = proc_open($cmd, array(
                0 => array('pipe', 'r'),
                1 => array('pipe', 'w'),
                2 => array('pipe', 'w')
        ), $pipes);
stream_set_blocking($pipes[1], 0);
stream_set_blocking($pipes[2], 0);

fwrite($pipes[0], $html);
fclose($pipes[0]);

$stdout = $stderr = '';
while (feof($pipes[1]) === false || feof($pipes[2]) === false) {
    $ret = stream_select(
        $read = array($pipes[1], $pipes[2]),
        $write = null,
        $except = null,
        $timeout = 1
    );
    if ($ret === false) {
        // error
        break;
    } else if ($ret === 0) {
        // timeout
        continue;
    } else {
        foreach ($read as $sock) {
            if ($sock === $pipes[1]) {
                $stdout .= fread($sock, 4096);
            } else if ($sock === $pipes[2]) {
                $stderr .= fread($sock, 4096);
            }
        }
    }
}
fclose($pipes[1]);
fclose($pipes[2]);
proc_close($proc);

まとめ

コマンドを静かにする(対策1)、エラー出力を捨てる(対策3)あたりが手軽。

エラー出力も取得する必要があって、出力、エラー出力がそれなりの量になる場合、対策5のような読み取り方をする必要がある。

関連するメモ

コメント