【php】proc_open()からのstream_get_contents()で処理が固まる場合
問題
あるコマンドを、phpで、proc_open()して、stream_get_contents()しようとしてるのですが、
コマンドの処理は終わった様子なのに、phpがいつまでも終わりません。
何が起きてるんでしょう。

答え
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のような読み取り方をする必要がある。