flock を使わない排他制御

man ページを見ると flock(2) は NFS 上のファイルのロックをしないとある。つまりそういうことなのだろう。

しかしそれで排他を諦めるわけにはいかない。flock(2) の代替として提示されている fcntl(2) によるロックは Ruby からは使用できない。できるのかもしれないが引数に渡す flock 構造体とやらの扱いが不明なので断念。

ロックディレクトリ方式も万全ではないことは確認済みだ。というか MOE は今その万全ではない状態で動いていて排他の破れも度々ログに記録されている。これまでクリティカルな事故は一度だけだが今後も十分起こり得るという状態は精神衛生上よろしくないので早急に何とかしたいと常々思っていた。


そんな経緯で、以前どこかで目にしたリネームロックというものを試してみた。
ロックファイルを用意し、ロックを掛ける時はロックファイルそのものをリネームしてしまうというやり方だが、実はそれ以上の内容はとんと覚えていない。というかその時は全く興味がなかったので読み飛ばしてしまったのだろう。仕方がないので車輪を再発明するしかない。

LOCKFILE = '.lockfile'
LOCKEDFILE = '.lockfile.locked'

def lock
  loop do
    begin
      File.rename(LOCKFILE, LOCKEDFILE)
      open(LOCKEDFILE, 'w').puts $$
      break
    rescue Exception
      sleep 1
    end
    begin
      pid = open(LOCKEDFILE, 'r').read.to_i
      unless pid == $$
        next if Process.kill(0, pid) rescue false
      end
      File.rename(LOCKEDFILE, LOCKFILE)
    rescue Exception
    end
  end
end

def unlock
  File.rename(LOCKEDFILE, LOCKFILE)
end

テストしてみた範囲ではこれで相当頑丈になったようだ。
心配なのはロックしっ放しでプロセスが死んだ場合の後始末。リネーム後のファイルから該当プロセス ID を取得するやり方がイマイチ美しくないように思えるが。さて。