CGIスクリプト配布 - CGI WEB


Home back
ファイルロック





■ファイルロックの必要性
CGIはローカルなアプリケーションと違い不特定多数の人がプログラムを同時に実行する場合があります。 WEB上で動作するプログラムですから当然の事です。 この事によりCGIではログファイルの破損が起きやすくなります。

例えば掲示板のCGIで投稿ボタンが押された場合、ログファイルを更新しますがAさんの投稿記事をログファイルに書き出す為に書き込み専用オープンしたと同時にBさんの投稿記事を更新する為の前処理としてログファイルを読み込んだ場合を考えてみます。

Aさんが書き込み専用オープンした時点でファイルは空になっています(ファイル操作項目参照)。 これをBさんが読み込みログファイルの更新処理をしている間にAさんがログファイルを書き込みます。 この後にBさんの更新処理が完了しログファイルを書き込みした場合、最終的なログファイルの内容はAさんの投稿記事はおろか過去の投稿記事は全て消滅し、Bさんの投稿記事のみが残るという事になってしまいます。

これを避ける為には「ログの読み込み⇒ログの更新処理⇒ログの書き込み」という一連の処理に入る前に「Aさんが入っている場合はBさんを待機させ、Aさんが出た後にBさんを入れる」ようにすれば良い事になります。

■mkdir関数によるファイルロック方法
mkdir関数によるファイルロック方法を紹介します。 mkdir関数はディレクトリを新規作成する関数です。 同じ名前のディレクトリを作成しようとするとエラーになりますのでこれを利用します。

Aさんが一連の処理に入る時に例えば「lock」という名前のディレクトリをmkdir関数で作成しておきます。 ここにBさんが入ろうとした場合、既にAさんがディレクトリを作成していますのでエラーが発生します。 エラーが発生した場合はそのまま待機させます。 やがてAさんの一連の処理が終わり「lock」ディレクトリをrmdir関数で消去します。 ここで初めてBさんがmkdir関数で「lock」ディレクトリを作成する事が出来ますので一連の処理に入ってもらいます。

□ファイルロック処理の流れ

$lock  = './lock';   # ロックディレクトリ
$logfile = './bbs.log'; # ログファイル

# ファイルロック開始
&lock;

open(IN, "<$logfile") || die;
@log = <IN>;
close(IN);

 (ログファイルの更新処理)

open(OUT, ">$logfile") || die;
print OUT @new; # 更新処理後のログ書き込み
close(OUT);

# ファイルロック解除
if (-e $lock) { rmdir($lock); }

exit()

# ファイルロックサブルーチン
sub lock {
  if (-e $lock && (stat(_))[9] + 60 < time) { rmdir($lock); }
  for (1..20) {
    if (mkdir($lock, 0755)) { return; }
    select undef, undef, undef, 0.5;
  }
  &error('Error:ファイルロック');
}

ポイントは「ログの読み込み⇒ログの更新処理⇒ログの書き込み」の一連の処理中はロックをかけたままにする事です。 まず最初にロックをかけ、ログファイルを配列@logに読み込みファイルを閉じます。 次にログファイルの更新処理を行い、更新処理が終わったログファイル(@new)を書き込みます。 そしてロックの解除を行います。

「ファイルロックサブルーチン」について説明します。 はじめにロックディレクトリが存在するかどうかを判定しています。 存在した場合、そのディレクトリが1分以上古いものである場合は何らかの原因で残ってしまったロックディレクトリであると判断して消去します。 これは永久にロックディレクトリが作成出来ないという状態を回避する為のものです。

次にmkdir関数でパーミッション値を「755」としたロックディレクトリを作成します。 作成出来た場合はメインルーチンに戻り一連の処理に入ります。 作成出来なかった場合は既に一連の処理に入っている人がいる事になりますのでselect関数で0.5秒待機し、再度ロックディレクトリの作成を試みます。 これを計20回繰り返し、最大で10秒待機させるようにしています。 20回繰り返しても作成出来ない場合は「ファイルロックエラー」として処理を抜けます。

□mkdir関数

mkdir(作成するディレクトリ名, パーミッション値);

パーミッション値は8進数で指定する事になっています。 Perlで8進数を表すには頭に0を付けますので「0755」等と指定します。

□select関数

select undef, undef, undef, 0.5;

0.5秒ウエイトをかける事が出来ます。 select関数は本来はウエイトをかける為のものではありませんが、このように使っている方が多いので真似をしてます。 1/100秒単位で指定可能です。 他にはsleep関数を使う方法もあります。

□sleep関数

sleep(1);

1秒ウエイトをかける事が出来ます。 こちらは秒単位で指定します。

■ファイルロック処理の注意事項
ファイルロック処理は上記のスクリプト例のようにロックディレクトリを作成してからログファイルを開きますのでログファイルのオープンに失敗した場合はロックディレクトリが残ってしまう事になります。 オープンに失敗した場合はロックディレクトリを削除するような処理を付け加える事が必要です。

■その他のファイルロック方法
上記で紹介したmkdir関数によるファイルロック方法の他にflock関数やsymlink関数を使用してファイルロックを行う事も出来ますが、これらはUNIX特有の関数ですのでWindowsサーバでは使用する事が出来ません。 mkdir関数はUNIXサーバやWindowsサーバでサポートされている関数ですので当サイトで配布するCGIは全てこのファイルロック方式を採用しています。



□更新履歴
 2007.04.26 全体的に内容を見直し




CGI WEB