[ruby-list:46580] Re: IO#flock

From: "NARUSE, Yui" <naruse@...>
Date: 2009-11-30 14:51:04 UTC
List: ruby-list #46580
成瀬です。

5.5 wrote:
> IO#flock について,どうも腑に落ちない点があります。

まず、flock とはなんぞや、を理解する必要があります。

以下の「Perlの排他制御」の説明がわかりやすいと思うのですが、
flock は「ファイルをロックする仕組み」ではなく、
「ファイルをロックしているよと教えるための仕組み」なのです。
http://homepage1.nifty.com/glass/tom_neko/web/web_04.html#flock

なので、flockを使っていないプロセスには意味がありません。

> -- lock1.rb --
> open("log.txt", "a+") do |f|
>   f.flock File::LOCK_EX
>   f.puts 1
>   sleep 5
>   f.puts 2
> end
> ------
> 
> -- lock2.rb --
> open("log.txt", "a+") do |f|
ここにも f.flock File::LOCK_EX が必要です。

>   f.puts 3
> end
> ------

> lock1.rb を走らせた直後に lock2.rb を走らせれば,以下のように
> 動作する,と期待して実験しました。
> 
> [1] まず lock1.rb が 1 を書こうとする。flush はしてないので,
>  バッファーに書かれるのみ。
> [2] lock1.rb は 5 秒間のスリープに入る。
> [3] lock2.rb が 3 を書こうとするが,ロックされているので,書
>  けない。待機する。
> [4] lock1.rb が 2 を書こうとする。ファイルを閉じる際に実際の
>  書き込みが行われる。
> [5] lock1.rb によるロックが解除される。
> [6] ロックが解除されたため,lock2.rb が 3 を書いてファイルを閉
>  じる。

既にお気づきかも知れませんが、flock はファイルを開いた後で行われています。
つまり、a+ を使った場合、一度開いた後で、他のプロセスによって、
ファイルの末尾にデータが追加されているということになります。
この結果、現在のファイル上の位置が不定になり、実装によって差が出ます。

> その結果,log.txt には
> 
> ------
> 1
> 2
> 3
> ------
> 
> と書かれるはず,と思いました。

以上の通り、この期待は成り立ちません。

> 次に,lock2.rb に 1 行追加して以下のようにし,
> 
> -- lock3.rb --
> open("log.txt", "a+") do |f|
>   f.flock File::LOCK_EX
>   f.puts 3
> end
> ------
> 
> lock1.rb → lock3.rb の順に走らせました。
> 
> すると,mswin187 では期待どおり 1, 2, 3 の順に書かれましたが,
> mac187 ではいつまでたっても両方のスクリプトが終了せず,
> mswin191 では 1, 2 だけが書かれて以下の例外が発生しました。
> 
> lock3.rb:2:in `flock': Invalid argument - log.txt (Errno::EINVAL)
>         from lock3.rb:2:in `block in <main>'
>         from lock3.rb:1:in `open'
>         from lock3.rb:1:in `<main>'
> 
> この EINVAL の意味を
> http://msdn.microsoft.com/ja-jp/library/5814770t.aspx
> で調べたところ,
> 
>> 不正な引数。関数の引数のいずれかに無効な値が指定されています。
>> たとえば、fseek 呼び出しでファイル ポインタを移動するとき、
>> 指定した元の位置がファイルの先頭より前にある場合です。
> 
> と書かれていましたが,なんだかよく分かりません。

これが上述の話です。
結局の所、flock を使ってロックしながらファイルに追記する場合、
以下のようにする必要があります。
  open("log.txt", IO::RDWR|IO::CREAT) do |f|
    f.flock(IO::FLOCK_EX)
    f.seek(0, IO::SEEK_END)

なお、ファイルが常に存在すると仮定できる場合、
IO::RDWR|IO::CREAT を "r+" と書くことができます。

つまり、こちらに関しては flock の使い方の問題です。

> 次に,PStore ではどうなのだろうと,二つのプロセスから一つの
> ファイルに PStore でデータを書こうとしたところ,mswin191 では
> やはり同じ例外が発生しました。(再現コードは略します)

で、PStore はちゃんと手当してるはずかつ、手当しているべきで、
もし普通に使っていて Errno::EINVAL が出るならばバグのように思います。
こちらの再現コードいただけませんか。

-- 
NARUSE, Yui  <naruse@airemix.jp>

In This Thread