11

ノンブロッキング ロックを要求するにはどうすればよいですか?

ファイルを個別にロックしようとすると、 Ruby のFile#flockが期待どおりに機能しないのはなぜですか? ブロック内のファイルをロックすることは、この問題の正しい解決策ではありません。ポイントは、永続的なロックでのロックの動作を示すことだからです。ブロック内で File#flock を使用すると、ブロックの終了時にロックが解除されるため、問題が適切に示されません。

File#flock は、特にノンブロッキング ロックを要求する場合に、さまざまな方法で失敗します。以下にいくつかの例を示します。

File#flock の失敗例

  • #flock はロック要求をタイムアウトにする方法を提供しないため、複数の排他ロックを使用すると無限に待機します。

    # First lock succeeds.
    f1 = File.open('foo', File::RDWR|File::CREAT, 0644)
    f1.flock(File::LOCK_EX)
    # => 0
    
    # This never returns.
    f2 = File.open('foo', File::RDWR|File::CREAT, 0644)
    f2.flock(File::LOCK_EX)
    
  • ファイルが排他的にロックされているときに非ブロッキング ロックを要求すると、無効な引数の例外が発生します。

    f1 = File.open('foo', File::RDWR|File::CREAT, 0644)
    f1.flock(File::LOCK_EX)
    # => 0
    
    f2 = File.open('foo', File::RDWR|File::CREAT, 0644)
    f2.flock(File::LOCK_NB)
    # => Errno::EINVAL: Invalid argument - foo
    
  • ドキュメントには、#flock は「locking_constant (以下の表の値の論理和) に従ってファイルをロックまたはロック解除する」と記載されています。ただし、論理 OR はプラットフォームによってはErrno::EINVALorを発生させます。Errno::EBADF

    f1 = File.open('foo', File::RDWR|File::CREAT, 0644)
    f1.flock(File::LOCK_EX)
    # => 0
    
    f2 = File.open('foo', File::RDWR|File::CREAT, 0644)
    f2.flock(File::LOCK_NB || File::LOCK_EX)
    # => Errno::EINVAL: Invalid argument - foo
    

ネイティブ File#flock ソリューションを推奨

排他ロックを取得できない場合はTimeout モジュールを使用して発生させることTimeout::Errorができますが、File#flock はこの問題をネイティブに解決できるはずです。では、実際にブロックせずに排他ロックを要求するにはどうすればよいのでしょうか?

4

1 に答える 1

17

排他ロックでタイムアウト モジュールを使用する

Timeout モジュールを使用して、#flock が排他ロックを取得する期間を設定できます。次の例ではTimeout::Error: execution expiredが発生しますが、アプリケーションに適していると思われる方法でレスキューできます。タイマーが切れたときにnilを返すと、 #flock 式が真であるかどうかをテストできます。

require 'timeout'

f1 = File.open('foo', File::RDWR|File::CREAT, 0644)
f1.flock(File::LOCK_EX)
# => 0

f2 = File.open('foo', File::RDWR|File::CREAT, 0644)
Timeout::timeout(0.001) { f2.flock(File::LOCK_EX) } rescue nil
# => nil

ノンブロッキング ロック試行にビットごとの OR を使用する

File#flockのドキュメントには次のように書かれています。

locked_constant (以下の表の値の論理和) に従って、ファイルをロックまたはロック解除します。File::LOCK_NB が指定されていて、そうでなければ操作がブロックされる場合は、false を返します。

ただし、このメソッドは実際には、tOROP パーサー トークンによってparse.y で定義されている論理 OR キーワードではなく、ビット単位のOR演算子を想定しています。その結果、排他ロックが失敗したときに#flock が戻ることを許可する正しい引数は、実際にはです。例えば:falseFile::LOCK_NB|File::LOCK_EX

f1 = File.open('foo', File::RDWR|File::CREAT, 0644)
f1.flock(File::LOCK_EX|File::LOCK_NB)
# => 0

f2 = File.open('foo', File::RDWR|File::CREAT, 0644)
f2.flock(File::LOCK_NB|File::LOCK_EX)
# => false

f1.close; f2.close
# => nil

これにより、利用可能な場合は一貫して排他ロックが生成されます。それ以外の場合は、例外を発生させたりレスキューしたりするオーバーヘッドなしで、すぐに偽の値を返します。これは明らかにモジュールの意図された使用方法ですが、ドキュメントでは理解しやすくするためにいくつかの説明と追加の例を使用できます。

于 2013-03-08T23:04:54.260 に答える