Skip to content
This repository has been archived by the owner on Apr 19, 2018. It is now read-only.

Commit

Permalink
make object writes atomic
Browse files Browse the repository at this point in the history
When Grit writes a loose object via the LooseStorage class,
it just opens the object file and starts writing. This works
most of the time, but can be a problem in some corner cases,
including:

  1. If another process tries to write the same object
     simultaneously, the writes may be interleaved and the
     object can be corrupted.

  2. If another process tries to read the object
     simultaneously, it may see the object in a half-written
     state.

  3. If the process or machine crashes during the write, we
     may leave a half-written corrupt object.

This can be solved by writing the object to a temporary file
and linking it into place. This is the same strategy used by
git itself.
  • Loading branch information
peff committed Feb 22, 2013
1 parent 75cf040 commit 00a0054
Showing 1 changed file with 19 additions and 3 deletions.
22 changes: 19 additions & 3 deletions lib/grit/git-ruby/internal/loose.rb
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@
require 'zlib'
require 'digest/sha1'
require 'grit/git-ruby/internal/raw_object'
require 'tempfile'

module Grit
module GitRuby
Expand Down Expand Up @@ -60,6 +61,23 @@ def get_raw_object(buf)
return RawObject.new(type, content)
end

# write an object to a temporary file, then atomically rename it
# into place; this ensures readers never see a half-written file
def safe_write(path, content)
Tempfile.open("tmp_obj_", File.dirname(path), :opt => "wb") do |f|
f.write content
f.close
begin
File.link(f.path, path)
rescue Errno::EEXIST
# The path already exists; we raced with another process,
# but it's OK, because by definition the content is the
# same. So we can just ignore the error.
end
f.unlink
end
end

# currently, I'm using the legacy format because it's easier to do
# this function takes content and a type and writes out the loose object and returns a sha
def put_raw_object(content, type)
Expand All @@ -76,9 +94,7 @@ def put_raw_object(content, type)
content = Zlib::Deflate.deflate(store)

FileUtils.mkdir_p(@directory+'/'+sha1[0...2])
File.open(path, 'wb') do |f|
f.write content
end
safe_write(path, content)
end
return sha1
end
Expand Down

0 comments on commit 00a0054

Please sign in to comment.