PHPの本家サイトでflockの説明を読んでいたら、以下の変更履歴に気がつきました。
5.3.2 ファイルのリソースハンドルを閉じたときにロックを自動的に解放する機能が削除されました。 ロックの解放は、常に手動で行わなければなりません。
ところがネットの解説を見ると、ロック開放はflock($fp, LOCK_UN); ではなく、fcloseでやれとしている解説が結構あります。
(4)fcloseの前にflock解除するな
…fcloseの前にflock(ファイルポインタ, LOCK_UN) する人は実に多いのですが、これははっきりと間違いだと断言します
LOCK_UNは普通は使われない。ロック開放はfclose()関数でやるのが鉄則。
最初に引用した解説では、理論的な根拠を説明していてその内容は正しいのですが、その対処として明示的なflock解除をするなと書いているわけです。
しかし、PHP5.3.2以降では、正式なマニュアルに「ロックの解放は、常に手動で行わなければなりません」と書かれているのですね。
twitterで教えていただいたところでは、これはPHPのバグ修正として行われた変更のようです。
@ockeghem @haruyama この変更はロック中のファイルを二重に開きfcloseを呼んだときに意図せずunlockされるのを防ぐbugfixです。ライブラリとして呼ばれる可能性のあるコードでは強制的にLOCK_UNするのではなく単にfcloseすべきです。
— ko-zuさん (@cause_less)12月 3, 2012
ライブラリの中で「勝手に」LOCK_UNすると、おかしなことの原因になるということですね。
@rskyさんがPHPのソース差分を調べてくださいました。
@ockeghem gist.github.com/4192952 こんな感じで php_stream_lock(stream, LOCK_UN); がなくなってますね
— Ryusuke SEKIYAMAさん (@rsky)12月 3, 2012
該当部分のみを引用しますと、以下のように、明確にアンロック処理が削除されています。
一方、明示的にアンロックしていない場合、どういう時におかしくなるかですが、これは@haruyamaさんに教えていただきました。
@ockeghem 関連するすべてのファイル(リソース,デスクリプタ)がclose()されれば, 結局ロックは開放されます. ほとんどのPHPのスクリプトでは挙動が変わらないでしょう.d.hatena.ne.jp/Gimite/2006042… の場合は5.3.2以降でダメになったのかな
— HARUYAMA Seigoさん (@haruyama)12月 3, 2012
参照している先には、rubyのサンプルが載っていますが、PHPで書き直してみました。
// a.php <?php $fp = fopen('a.txt', 'r+'); // ファイルをopen flock($fp, LOCK_EX); // ロックする system('(php b.php > log.txt) >/dev/null &'); // バックグラウンドでb.phpを実行 // fflush($fp); // (B) // flock($fp, LOCK_UN); // (A) fclose($fp); // ファイルを閉じる echo "done a.php\n";
// b.php
<?php echo "start b.php\n"; $fp = fopen('a.txt', 'r+'); flock($fp, LOCK_EX); // ロックする fclose($fp); echo "done b.php\n";
実行してみると、PHP5.3.1ではa.phpのロックがfclose時にアンロックされてb.phpが動き出しますが、PHP5.3.2ではロックが残り、b.phpはロックされたままになります。PHP5.3.2でも、上記(A)とコメントアウトしたflock($fp, LOCK_UN)で明示的にアンロックするとb.phpも動くようになります。
ということで、マニュアル通り明示的なアンロックをすれば、すべてのバージョンのPHPで正しく動くようになります。
ところで、元々どうして「明示的にアンロックするな」という話が出てきたかというと、上記のサンプルには出て来ませんがロックした状態でファイルに書き込みをしたい場合、アンロックしてからfcloseするまでの間に、バッファに残っていたデータが *ロックされない状態で* ファイルに書き込まれる可能性があるからです。したがって、アンロックの前に、明示的にfflushを呼び出してバッファの内容をはき出すようにすべきです。ソース上では(B)のコメントアウトした部分を活かせば大丈夫です。
ということで、ファイルのロックに関しては、
- 明示的にflock($fp, LOCK_UN); でアンロックする
- アンロックする前に fflush($fp); でバッファの内容をはき出す
ことが必要です。
kani-develがockeghemからリブログしました
ruriwoがこの投稿を「スキ!」と言っています
moriwakaがこの投稿を「スキ!」と言っています
peketaminがkiri2からリブログしました
peketaminがこの投稿を「スキ!」と言っています
act2012blがatm09tdからリブログしました
sharkppがockeghemからリブログしました
poroshiriがこの投稿を「スキ!」と言っています
sarabandejpがこの投稿を「スキ!」と言っています
elfがockeghemからリブログしました
ikeisukeがこの投稿を「スキ!」と言っています
dara-jがockeghemからリブログしました
zouma3がockeghemからリブログしました
sky-windがockeghemからリブログしました
samaiがockeghemからリブログしました
atm09tdがこの投稿を「スキ!」と言っています
atm09tdがockeghemからリブログしました
karakaniがこの投稿を「スキ!」と言っています
clairvy-blog-blogがこの投稿を「スキ!」と言っています
cworld2kがockeghemからリブログしました
ockeghemの投稿です