同時書き込みによるファイルの破損を防止するには?

[上に] [前に] [次に]
ドナドナ 1998/02/25(水) 11:52:02
flockで行う方法、ロックファイルやロックディレクトリを
作る方法など色々な方法があると思いますが、
その方法のメリット・デメリットを教えて下さい。
もし一番確実な方法が存在するなら、それも教えて下さい。
お願いします!!

miyasiro [E-Mail] 1998/02/25(水) 16:31:00
 「ページ記入で排他制御をする際、排他制御を解除するには? 」でのとほほさんやB-Cusさんの書き込みを読んでいろいろ勉強させて頂いたので、これをまとめてみました。UNIXはよく分からないので、フォローをよろしくお願いします。

●通常のファイルをロックファイルに使用する
・欠点
 if (-f $lockfile)でロックを検査した後、open(LF,">$lockfile")でロックを設定するので、これら検査と作成の間の隙間に他のプロセスが重複してアクセスする可能性がある。つまり、ロックが不完全になるおそれがある。

●symlink
・利点
 if symlink($datafile,$lockfile)でロックの検査と設定とがシステムコールレベルで同時に行われるので、これらの間に隙間が生じない。
・欠点
 UNIXに限定され、symlink()をサポートしないシステムもある。

●mkdir(とほほさんの方法)
・利点
 symlink()と同じ利点に加えて、すべてのUNIXで利用可能で、さらにWindowsでも使える。

●flock
・利点
 システムが提供するロック機構であるため、確実なロックが期待でき、使い方も簡単。
・欠点
 UNIXに限定され、flock()をサポートしないシステムもある。
 flock()を呼び出すと、ロックが解除されるまで制御が戻らないので、一定時間以上待たされた場合に処理を中止してブラウザに戻るというようなやり方(とほほさんの方法で採用)ができない。
・個人的な疑問点
 プロセスがロック中にダウンしたりしたような場合、このロックが確実に解除されるかどうかが分からない。解除されないと後のプロセスは永久に待たされることになる?
 symlink()やmkdir()なら、後のプロセスがロックの設定日時を調べて、十分古い場合には強制的にロックを解除することができる(とほほさんの方法で採用)。


 あと、同時書き込みとは関係ないのですが、書き込み中にサーバダウンが発生すると、ファイルが破壊される可能性がありますよね。とほほさんが一旦データを .tmpファイルに書き出して、後から .datファイルにコピーするようにしているのは、この場合のバックアップのためでしょうか?

ドナドナ 1998/02/25(水) 18:04:50
miyasiroさん、早速詳しい解説ホントにありがとうございました!!
これを見るととほほさんが使用されているmkdir()が一番良いように思われますね。
今後この方法でやっていきたいと思います。

さて、ロックディレクトリですが、
一つのデータファイルを複数のプログラムで
使う場合はやはり同じディレクトリ名にしたほうがいいですか。

また、ラウンジのスクリプトを参考にさせて頂こうとかんがえているのですが、
どうしても分からないところがあるので、教えていただけないでしょうか。
「ファイルロック開始」の部分です。
sub fileLock {
    if ($file_lock_flag) {
        for ($i = 1; $i <= 6; $i++) {
            if (mkdir($lock_file, 755)) {
                last;
            } elsif ($i < 6) {
                sleep(2);
            } else {
                return(undef);
            }
        }
    }
    return(true);
}
というものですが、return(undef);とreturn(true);という命令が
具体的に何をしてるのか分かりません。
どうぞよろしくお願いします。

miyasiro 1998/02/25(水) 22:51:15
>また、ラウンジのスクリプトを参考にさせて頂こうとかんがえているのですが、
ということで、ラウンジのスクリプトを覗いてみたのですが、ゲストブックや掲示板とはまた少し違ってますね。
それはともかく、ご質問に形式的に回答するならば、return(undef)は、サブルーチンfileLockの呼び出し元に未定義値を返し、return(true)は、trueを'true'と解釈してこの文字列を返します。ラウンジのスクリプトでは、これらの返り値は利用されていませんが、(従って、ラウンジの場合は、mkdir()を6回失敗しても書き込みが行われるようです)
 $ret = &fileLock;
 if ( defined( $ret ) ) { データファイルの更新処理 }
 else { データファイルの更新を行わずに終了処理 }
とすれば、ロックに成功した場合と失敗した場合で、処理を分岐させることができます。(defined(&fileLock)でもいいと思ってやってみたらダメだった…サブルーチン自体の定義/未定義を検査してるのかな?)
いずれにしても、普通はreturn(0)やreturn(1)でいいと思いますが…

>一つのデータファイルを複数のプログラムで
>使う場合はやはり同じディレクトリ名にしたほうがいいですか。
同じ名前にしないと、ロックにならないと思います。

とほほ 1998/02/25(水) 23:27:36
flock()の使い方についてちょっと調べてみました。
  open(IN, "file.txt");
  flock(IN, $mode);
のように使用します。$modeは、
  1: 読み込みロック。ブロッキングモード。
  2: 書き込みロック。ブロッキングモード。
  5: 読み込みロック。ノンブロッキングモード。
  6: 書き込みロック。ノンブロッキングモード。
  8: ロックを解除する。
誰かが読み込みロック中は、書き込みロックが禁止されます。
誰かが書き込みロック中は、書き込みロックも読み込みロックも禁止されます。
ブロッキングモードだと誰かがロック中はずっと待ちます。
ノンブロッキングモードだと誰かがロック中だとすぐにエラーで戻ります。
プログラムが途中でダウンした場合は、確実にロックは自動解除されます。
・・・という訳で、flock()が使えるなら、flock()が一番確実です。

return(undef), return(true)については・・・私が無知だった頃の
不要コードの残骸です。(^_^;)削除していただいて結構です。

miyasiro 1998/02/26(木) 02:21:09
 フォローありがとうございます > とほほさん

>  6: 書き込みロック。ノンブロッキングモード。
 ノンブロッキングモードは知りませんでした。(手元の本には$modeは1,2,4,8しかないと書いてあった…)それから、ダウン時も確実にロック解除されるということなので、flock()の欠点は、「サーバーによっては利用できない」ということだけになりますね。
>・・・という訳で、flock()が使えるなら、flock()が一番確実です。
 うちも、flock()に変えよう。

 そんなわけで、もう不要になったかも知れませんが、掲示板とかのやり方も加味してsub fileLockを書き換えてみました。&fileLockが真を返した場合にのみデータファイルを更新して最後にrmdir( $lock_file );を実行します。
# 行頭に全角空白があるので注意!
sub fileLock {
 if ( $file_lock_flag ) {
  foreach $i ( 1 .. 6 ) {
   last if mkdir($lock_file, 755); # ロックに成功すれば true を返す
   return 0 if $i > 5; # ロックに失敗したら false を返す
   if ( $i == 1 && (stat($lock_file))[9] < time - 600 ) {
    rmdir( $lock_file ); # 10分以上前のロックは強制的に解除する。
    next; # 次のループでロックが成功する筈
   }
   sleep(2);
  }
 }
 return 1;
}

ドナドナ 1998/02/26(木) 10:15:15
すごくよく分かりました!
丁寧な解説を本当どうもありがとうございました。
これを参考にやっていきたいと思います。

最後に・・・flock()が使えるか使えないかは
プロバイダに聞けば分かりますか?
プログラムを動かしているサーバーがサポートしていればいいんですよね。

miyasiro 1998/02/26(木) 18:43:16
>最後に・・・flock()が使えるか使えないかは
eval を使って下のような perl script を実行してみれば確認できます。
open( FH, ">$file" ) || die;
eval 'flock(FH,6)';
if ( $@ eq '' ) { print "OK!\n"; }
else { print "NO: $@\n"; }

うちも OK! でした。
ただ、open(FH,"<$file");flock(FH,5);の読み込みロックが成功しません。書き込み時にしか使えないのかなぁ?

ドナドナ 1998/03/02(月) 09:43:28
[[解決]]
miyasiroさん、とほほさん、回答ありがとうございました。
すごく勉強になりました★
flock(IN,5);のことはちょっとわかりませんが、
一応解決にしておきますね。
また何かあったらよろしくお願いします!

[上に] [前に] [次に]