file_put_contents()中にPHPのタイムアウトでファイル破損
しばらくご無沙汰していました。そしてまた技術ネタです。今回話す内容はタイトルの通りで、タイトルで意味が分かった方は特に続きを見る必要はないです。
MODXでキャッシュファイルが時々壊れるという謎の現象が発生していて「なんでだろう」と思い、コードを見ながら一つ仮説を立てました。
キャッシュは作成後にfile_put_contents()関数を使ってファイルへ保存していて、PHPの関数的には1行で終わっています。PHPにはmax_execution_timeで実行時間の設定ができ、この上限を越えたPHPは処理を中断させられます。そしてこの中断はfile_put_contents()の内部処理の途中でも容赦なく中断するのではないかという仮説です。
というわけで実際に検証してみます。まずは5M程度のダミーファイルを要します。
次のphp.iniをいじって実行時間(max_execution_time)を短くします。
apaheの再起動を忘れずに。
※そういえばコマンドラインで検証しようと思ったのですが、コマンドラインだとmax_execution_timeが無制限になるみたいで諦めました。
次のPHPコードを書いてWebからアクセスします。
コードの中身は5Mのdummy.txtをひたすらcopy.txtに何度もコピーするだけです。このままだと延々に続けますがmax_execution_timeを2秒に設定しているので2秒後に強制的に終了します。ブラウザからアクセスすると2秒程度で処理が終わっていることが確認できます。その後、実際のcopy.txtを見てみるとタイミングによってきちんと5Mだったり、0バイトだったり、もしくは3M程度の中途半端な容量になっている事があります。つまり運が悪いとファイルが意図せず壊れています。
この事からmax_execution_timeに設定した時間が経過するとfile_put_contents()がファイルを作成中でも容赦なく中断する動作のようです。個人的にはphp_put_contents()は一つの関数なのできちんと完結してほしいと思っていたのですが、そうではないようで…。
回避方法はシンプルで別ファイル名で保存した後にファイル名を変更させます。コードは次の通り。
このコードは何度実行してもcopy.txtは壊れません。その代り、copy.txt.xxxxというファイルが残ったりします。が、ただのゴミファイルなので定期的に消す処理をいれるか、放置プレイでもいいと思います。rename()はたぶん内部的にstdio.hのrename()でも使ってるんじゃないかと思うので、copy.txtが変になる事はないかなと(未確認)。
あとは、この違うファイル名でいったん作ってリネームする処理をfile_put_contents()が実装してくれればいいのになーと思ったり。そしたら気兼ねなくfile_put_contents()が使えるのになぁ。
file_put_contents()の処理中でも容赦なく処理を中断するmax_execution_time設定ですが、sleep()関数については華麗にスルーするようです。sleep(100)で発生する100秒の時間はカウントしないという。そこはカウントしてくれてもいいんじゃないかと思ったり。