flock(LOCK_EX) で EBADF

ちょっとハマったのでメモ。

ファイルを flock() で排他的にロックするために次のようにすると成功します。

% ruby -e 'File.open("hoge").flock(File::LOCK_EX); puts "OK"'
OK

が、NFS 上で同じことをやると失敗します。

% ruby -e 'File.open("hoge").flock(File::LOCK_EX); puts "OK"'
-e:1:in `flock': Bad file descriptor @ rb_file_flock - hoge (Errno::EBADF)
    from -e:1:in `<main>'

7年前に書いたんですが、Linux は NFS ファイルシステムに対して flock() すると fcntl(F_SETLK) を使います。

で、fcntl(F_SETLK)flock() と異なり、排他ロックをするにはファイルを書き込み可のモードでオープンしておかないといけません。

通常のローカルファイルシステムでも読み込み専用でオープンしたファイルに fcntl(F_SETLK) を使って排他ロックを行えば同じエラーが発生します。

% ruby -rfcntl -e 'File.open("hoge").fcntl(Fcntl::F_SETLK, [Fcntl::F_WRLCK, 0].pack("ss"))'
-e:1:in `fcntl': Bad file descriptor @ rb_fcntl - hoge (Errno::EBADF)
    from -e:1:in `<main>'

書き込みできるモードでオープンしておけばエラーにはなりません。

% ruby -rfcntl -e 'File.open("hoge", "r+").fcntl(Fcntl::F_SETLK, [Fcntl::F_WRLCK, 0].pack("ss")); puts "OK"'
OK

つまり、NFS 上でも書き込みできるモードでファイルをオープンしておけば flock() で排他制御できます。

% ruby -e 'File.open("hoge", "r+").flock(File::LOCK_EX); puts "OK"'
OK

ということで、flock(LOCK_EX) する場合は書き込みできるモードでファイルをオープンしておいた方が無難というお話でした。