feofはワーナーなエンターテイメント 〜無限ループという名のラビリンス〜

この記事はだいぶ前に書かれたものなので情報が古いかもしれません
エルシャダイ懐かしいな……やったことないけど

この記事を三行にまとめると

無限ループという名の迷路から永遠に抜け出せない
「おい、そんな処理で大丈夫か?」「大丈夫じゃない。大問題」
「迷宮のラビリンス」「記憶のメモリー」「力こそパワー」
話をしよう。

あれは、今から36万……いや、1万4千年前だったか。

まあいい、私にとってはつい昨日のできごとだが、君たちにとってはたぶん、1年半ほど前のできごとだ。



ということで、1年半ほど前だったと思うんですが、phpのfread()を使ってファイルをダウンロードする話をしました。

そんときの記事がこれ

僕は今でもこの方法を使ってダウンロード処理を実装することがあるんですが、この記事の書き方だと一つ、ささいでありながらも致命的にやばい問題があることに、1年半も経ってようやく気づきました。



致命的にやばい問題って?

とりあえず、前回も書いたその処理をここでもう一度。

$fp = fopen($url, 'rb');
while(!feof($fp)) {
    $buf = fread($fp, 1048576);
    echo $buf;
    ob_flush();
    flush();
}
fclose($fp);

ダウンロード処理自体はこれで大丈夫なんですが、実はこの方法だと、場合によってはサーバーが落ちるなんてこともあり得る。ガンマジヤベー処理なのです。

while文の条件にある「feof()」というのは、ポインタがファイルの終端にあるかどうかをチェックする関数です。ポインタが終端って何やねんって感じなんだけど、ようは「fopen()」で開いたファイルが最後まで読み込まれたかどうかを確認したりする関数です。読み込まれたらtrueが返ってくる。だから今回のような場合も、ファイルが全部読み込み終わったらループを抜けるって感じで使ってるんです。

では質問。



Q. もしポインタが永遠に終端に辿り着かなかったらどうなんの?
A. 無限ループという名の迷路から永遠に抜け出せないに決まってんじゃん。



デスヨネー。

じゃあ、ポインタが終端に辿り着かないことなんてあんのかって話なんだけど……もちろんある。

例えば、fopen()でファイルを開くのに失敗した場合ですね。この場合、ループの中にある「fread()」でもファイルを読み込めないので、ここで延々とエラーを出し続ける。こんな(↓)感じのエラーを吐き続ける。

fread() expects parameter 1 to be resource, boolean given in

そしてfeofもがtrueになることがないので、ループが止まらない。破滅への輪舞曲(ロンド)です。

こうなってしまうと延々に処理が終わらないしエラーは吐き続けるしで、サーバーにやばいくらいの負荷がかかる。CPU使用率が100%を超え、いずれは力を出しきった戸愚呂弟のように、100%を超えたひずみが発生する。すなわち、サーバーが落ちる。万が一落ちなかったとしても、エラーをログに吐き出すようにしていれば、ログファイルがサーバーのHDDを食らい尽くす可能性もある。ボリュームディスクの空き容量が0%になる。

これはよくないですよね。何ともよくない。そりゃルシフェルさんにも言われちゃいますよ。「おい、そんな処理で大丈夫か?」って。

大丈夫じゃない。大問題です。



ループを回避する

そもそもwhile文の条件にfeof()を使わないような処理にした方が良いんじゃないかって気もするんですが、とりあえず他の方法も思いつかないんで、feof()は使うけど無限ループを回避する方向で行きましょう。

無限ループにならない条件は、fopen()やらfread()で正常な結果が返ってきてくれれば良いわけですから、そこを判定する。例えばfopen()のところでファイルが正常に開けなかったら、ループ処理を行わないようにする。

こんな感じで行ってみましょう。

$fp = fopen($url, 'rb');

//ここに判定処理を追加
if(!$fp) {
  //正常に開けなかったら処理修了
  exit;
}

while(!feof($fp)) {
    $buf = fread($fp, 1048576);
    echo $buf;
    ob_flush();
    flush();
}
fclose($fp);

とりあえずこれなら、ファイルを開くのに失敗して迷宮のラビリンスから抜け出せなくなることはない。freadの方で判定する場合も同じ容量ですね。falseが返ってきたらループを抜けちゃうようにする。両方に処理を入れても良いのかもしれません。

これがベストな回避方法かは分かりません。ルシフェルさんに問われたら、ぜひ言い返したいですね。

「おい、そんな処理で大丈夫か?」
「一番良い処理を教えてくれ」






もし僕が1年半前に書いた記事を見て、コードをコピペしてダウンロード処理を実装したなんて人がいたら、こっちもあわせてご確認いただければと思っちょります。

ところで、迷宮のラビリンスって完全に意味被ってんだけど、語呂が良いと思わない? 僕の中で、意味が被ってるけど語呂や語感が良いと思う言葉ベスト3に入ってるんですよね。迷宮のラビリンス。

ちなみに他の二つは「記憶のメモリー」「力こそパワー」
 もしかしたら何か関連しているかも? 
 質問や感想などお気軽にコメントしてください