PHP 5.3.1 以前では fclose で明示的にアンロックされてしまう

この記事「PHP5.3.2以降ではfcloseで自動的にアンロックされない - 徳丸浩のtumblr」で PHP 5.3.2 から fclose で自動的に flock が呼ばれなくなったというのを知り衝撃を受けました。

衝撃だったのは PHP 5.3.1 以前は fclose で明示的に flock されている ということに対してです。 fclose で暗黙的にロックが解放されるというのは知っていましたが、てっきりファイルディスクリプタが複製も含めて全て閉じられたらロックも解放されることを言っているのだと思っていました。

しかも↓の差分とかを見た感じ、単に fclose から flock が削除されたのではなく、ストリームリソースの後処理から flock が削除されたっぽいからです。つまり、以前の PHP ではファイルのストリームリソースの後処理で明示的に flock が呼ばれていた、ということです・・・

この変更は不具合修正として行われたようですが、その不具合は以下のようなコードで再現出来ます。

<?php

$fp = fopen("a.txt" , "c+");
flock($fp, LOCK_EX);

$pid = pcntl_fork();

if ($pid < 0)
{
    exit("!pcntl_fork");
}
else if ($pid == 0)
{
    // 子プロセスは即終了・・・不憫な子・・・
    exit(0);
}

// ロックされているはず
rewind($fp);
$cnt = (int)fread($fp, 1024);
$cnt++;

// ロックされているので寝てても大丈夫!
sleep(1);

rewind($fp);
fwrite($fp, $cnt);
fclose($fp);

var_dump($cnt);

このコードは PHP 5.3.2 以降であればきちんと動きますが(例外処理とかが無いのはおいといて)、PHP 5.3.1 では flock が正しく効かず、同時に 2 本実行すれば同じ値が表示されます。

以前書いたこれ「forkするときの注意点 - ngの日記」と同じ問題です。子プロセスの終了時のリソースの後始末のときに、親プロセスで獲得していたロックまで解放してしまうためです。

このエントリでは、

ストリームリソースの場合は子プロセスで fclose されても大丈夫ですが、fork の前に必ず flash しておくべきです(書き込みがバッファされている状態で fork するとバッファまで複製されて、2重に書き込まれることがあるため)。

などと書いていますがこれは嘘でした・・・PHP 5.3.1 以前ではストリームリソースを flock した状態で fork すると意図せずロックが解放されてしまうことがあります(あと flush の綴りが違います(>_<))。

私が pcntl_fork を頻繁に使うようになったのは幸運にも 5.3.2 からなので多分大丈夫だと思いますが。。。


全然関係ないですが fopen に "c" なんて出来ていたんですね。