tohokuaikiのチラシの裏

技術的ネタとか。

proc_openの64kbの壁「slymak、それfile_get_contentsネタやない、proc_openや」

PHP Advent Calendar jp (day 15)

PHPerのみなさんならちょっとしたバッチやファイル操作なんかも全部PHPで書いてるPHP脳になってることと思います。もうShellScriptなんて古臭いものは使ってないことでしょう。

そんな時、システムコマンド使う時に大活躍するのがproc_openですね。プロセスの細かい処理ができ、かつエラーも取ることができ、大変便利です。

proc_openによるエラーの取得方法は以前ブログにアップしました。要約すると、system()の変わりにこれ使うとコマンドのエラーとリターンコードも得られますよっていう話です。

64kbの壁とは?

で、このproc_openを使いこなしてるPHPerの皆さんならご存知かもしれませんが、デスクリプタのSTDOUTに普通にパイプを使っていると、システムコマンドが64kb以上の出力を出してきた時にデッドロックが掛ります。クソですね。ちなみに、前回のエントリを書いた時に気付いていたのですがあえて無視しました。自分はコマンド自体でリダイレクトさせてごまかしました。

未確認ですが、多分、エラーメッセージが64kbを越えてもデッドロックが掛ると思います。

この壁を破るには

テンポラリファイルを経由

いちばん簡単なのは、STDOUTにしないで、テンポラリファイルに出力することです。*1
具体的には、さっきのエントリの関数で言うと

<?php
function system_ex($cmd, $stdin = "")
{
    $descriptorspec = array(
        0 => array("pipe", "r"),
/*        1 => array("pipe", "w"), */
        1 => array("file", "/tmp/w", "w"),
        2 => array("pipe", "w")
        );
...snip...
/*        while ($result = fgets($pipes[1], 1024)){
            $result_message .= $result;
        } */
        $result_message = file_get_contents("/tmp/w");

みたいにします。

stream_set_blockingを使う

64kで壁があるなら64kごとに分割すればいいんじゃないの?っていうのが正攻法でしょう。

ということで、stream_set_blockingを使います。具体的な使い方は知りません。stream_set_blockingみたいなマニアックな関数は使ったことがありません。具体的なコードはslymakさんが書かれてます。


というか、PHP Advent用にこのネタを取っておいたら、つい1週間前にマンマそのネタでslymakさんがPHPマニュアルに書かれてたので

「あー、どうしよう。勉強熱心なPHPerのみんなならきっともうこのコメント読んでて、『なんだよ、それslymakがPHPマニュアルにコメントしたパクリかよ』とかDISられるんだ・・・・」と数秒悩みましたが、

「ま、なぜかslymakさんfile_get_contentsの所に投稿してるし、だれも気付いてないだろうからいいか。」と判断したのは内緒です。



「slymak、それfile_get_contentsネタやない、proc_openや」


結構良いpostだけに惜しいです。

ちなみに何故64kかというのは

よくわかりません。64kという数字は実際に64kのファイルを作ってそれをcatするだけのコマンドをproc_openで実行させてみて、その前後で確かにデッドロックになるなというのを確かめて見つけた数字です。だれか原因を見つけたら教えてください。

追記

元エントリの方で、64kの壁を突破するためにtmpfile()を使う方法を追記しました。いやー、10年振りにすっきりしたわ。
tohokuaiki.hateblo.jp

*1:それ、リダイレクトでごまかしたのと同じやん・・・という反論は許可します。