サンプルで理解! フォームデータの受け渡し
1ページ目
menu.phpは、排他処理を扱ったようですが、早速ひどいです。
「一般的なオンラインシステムでは同時処理に対するデータの矛盾が発生しないよう留意する必要があります。」と書いているので、単にファイルが壊れなければいいという考えではないみたいなのに。
(1)flockするまえにデータを読むな
(2)file_existsのあとでwモードでファイルを開くな
(3)set_file_bufferはOSのバッファリングは関係ない
(4)fcloseの前にflock解除するな
(1)について。ファイルにはあらかじめ 1000000004 と書いてあるとします。
ユーザー1が4行目でファイルを開く
↓
ユーザー1が5行目で $order_no に 1000000005 を代入
↓
ユーザー2が4行目でファイルを開く
↓
ユーザー2が5行目で $order_no に 1000000005 を代入
ということで、ユーザー1とユーザー2の $order_no が一緒になってしまいました。
(1)を改善するとコードはこうなりますかね
$count_file="/tmp/count.dat";
if(file_exists($count_file)){
$file = fopen($count_file, "r+") or die("ファイルをオープンできませんでした");
set_file_buffer($file, 0);
flock($file, LOCK_EX);
$order_no = fgets($file) + 1;
}else{
$file = fopen($count_file, "w") or die("ファイルをオープンできませんでした");
flock($file, LOCK_EX);
$order_no = 1000000001;
}
rewind($file);
fputs($file, $order_no);
flock($file, LOCK_UN);
fclose($file);
(2)について。
file_exists と fopen の間はあいています。あいているということは、他のユーザーが割り込む可能性があるということです。
ユーザー1がfile_existsを調べる→ない
↓
ユーザー2がfile_existsを調べる→ない
↓
ユーザー1がfopen($count_file, "w")でファイルを開く(作成する)
↓
ユーザー2がfopen($count_file, "w")でファイルを開く
↓
ユーザー1の $order_no に1000000001を代入
↓
ユーザー2の $order_no に1000000001を代入
file_existsで調べても、その直後に状況は変わってるかもしれないということです。改善策はいくつかあります。
その1
$count_file="/tmp/count.dat";
touch($count_file);
$file = fopen($count_file, "r+") or die("ファイルをオープンできませんでした");
set_file_buffer($file, 0);
flock($file, LOCK_EX);
$order_no = fgets($file);
if(empty($order_no)) $order_no = 1000000001;
else $order_no++;
rewind($file);
fputs($file, $order_no);
flock($file, LOCK_UN);
fclose($file);
その2
$count_file="/tmp/count.dat";
$file = fopen($count_file, "a+") or die("ファイルをオープンできませんでした");
set_file_buffer($file, 0);
flock($file, LOCK_EX);
$order_no = fgets($file);
if(empty($order_no)) $order_no = 1000000001;
else $order_no++;
ftruncate($file, 0);
fputs($file, $order_no);
flock($file, LOCK_UN);
fclose($file);
(3)について
「set_file_buffer("ファイルポインタ", 0)」で書き込みバッファを使用しないようにします。書き込みバッファはOSがメモリ上に用意する一時的な記憶領域です。ハードディスクなど物理的なファイルシステムに書き出す前に利用されます。
バッファを利用することで、書き込みパフォーマンスを上げることができる一方、極めて短時間ですが、バッファ上のデータとファイルシステム上のデータに差異が発生します。その差異が発生している瞬間に障害が起きた場合、データが消失する可能性があります。そのため、バッファを利用せずに直接ファイルシステムに書き込むようにします。
と書いてますが、set_file_bufferはOSのバッファではなくPHPの内部のバッファーを制御します。マニュアルを見れば分かることなのに。「その差異が発生している瞬間に障害が起きた場合、データが消失する可能性があります。」とのことですが、差異が発生していないときに障害が起こってもデータは消失するわけで(「障害」ってのが具体的になんなのかさっぱりわかりませんが)。
ようするに、set_file_bufferは不要です。ただし、(4)を守るなら。
(4)について。
fcloseの前にflock(ファイルポインタ, LOCK_UN) する人は実に多いのですが、これははっきりと間違いだと断言します。flockをfcloseの前に解除するということは、fcloseの前に他のプロセスが割り込む可能性が出るということです。ファイルへの書き込みは、fwriteとかfputsとかしてからfflushまたはfclose実行までのどこかで行われる、というのがファイル周りのI/Oの基本です。なので、fcloseもロックの範囲内に入れなければなりません。fcloseでflockが解除されるのはそういう理由があるのです。単に「ファイルが閉じられたらflockもなにもないだろう」ではないのです。ちなみに、バッファを無効にしてこの問題を回避と言うのは俺に言わせれば邪道です。
さて、2ページ目にはファイルの排他処理なんてレベルじゃない重大な問題があります。それも書こうかと思いましたが、今日は疲れたのでこの辺にします。