プログラミング 美徳の不幸

Ruby, Rails, JavaScriptなどのプログラミングまとめ、解説、備忘録。

プロセスの排他制御

今作ってるGithub風のものはデータストアとしてRDBMSも使っているんですが、レポジトリ本体はファイルシステムとして、素のgitレポジトリで管理しています。


で、ファイルでデータを持つというのはウェブアプリではあまり聞かないような気がしますが(あっても画像だと思いますが),よくある軽量のスクリプトなどでデータベースを持つ程でもないものについては頻繁にtmpファイルを作成してデータを持つなどが普通ですから、意外に簡単に実装できます。

その上で怖いのはデータの整合性についてです。例えばあるユーザがstashを作成しようとしたときに、別のユーザがブランチを切り替えたらどうなるか。

アトミックな処理中は他の処理はそれを待つか、待っても終わらない場合は例外を返すようにしたい。

◯app/models/repository.rb

class Repository < ActiveRecord::Base
  def lock(&block)
    Timeout::timeout(10) do
      Dir.chdir(self.working_dir) do
        File.open(".lock","w") do |f|
          begin 
            f.flock(File::LOCK_EX)
            block.call
          ensure
            f.flock(File::LOCK_UN)
            File.unlink(f)
          end
        end
      end
    end
    rescue Exception => ex
      raise AccessDenied.new("timeout")
    end
  end
end

でもってこのレポジトリにロックをかけて一連の処理を保証したいときはlockにブロックを渡す。

@repository.lock do
  system("git stash")
  system("git checkout master")
  system("git stash pop")
end


とりあえずの保証としてはこれで良いと思うのだが、これはあるプロセスがレポジトリを操作している間に他のプロセスがそれに影響を与えないことを保証するわけで、一連の処理がアトミック=トランザクション処理になるわけではないはず。

つまりsystem("git stash")を実行しているときにプロセスが死んだらstashに不整合が起きる。どうしたものか。。。