Class: Amp::Repositories::Lock
Overview
Lock
Manages a given lock file, indicating that the enclosing folder should not be modified. Typically used during destructive operations on a repo (such as a commit or push).
We must be compatible with Mercurial’s lock format, unfortunately. Doesn’t life suck?
From Mercurial code, explaining their format:
lock is symlink on platforms that support it, file on others.
symlink is used because create of directory entry and contents are atomic even over nfs.
old-style lock: symlink to pid new-style lock: symlink to hostname:pid
Constant Summary collapse
- @@host =
nil
Instance Method Summary collapse
-
#apply_lock ⇒ Object
Applies the lock.
-
#initialize(file, opts = {:timeout => -1}) ⇒ Lock
constructor
Initializes the lock to a given file name, and creates the lock, effectively locking the containing directory.
-
#make_a_lock(file, info) ⇒ Object
Creates a lock at the given location, with info about the locking process.
-
#make_a_lock_in_file(file, info) ⇒ Object
Creates a lock at the given location, storing the info about the locking process in an actual lock file.
-
#read_lock(file) ⇒ String
Reads in the data associated with a lock file.
-
#release ⇒ Object
Releases the lock, signalling that it is now safe to modify the directory in which the lock is found.
-
#test_lock ⇒ Object
Text from mercurial code:.
-
#test_pid(pid) ⇒ Boolean
Checks to see if there is a process running with id
pid
. -
#try_lock ⇒ Object
Attempts to apply the lock.
Constructor Details
#initialize(file, opts = {:timeout => -1}) ⇒ Lock
Initializes the lock to a given file name, and creates the lock, effectively locking the containing directory.
36 37 38 39 40 41 42 43 |
# File 'lib/amp/repository/lock.rb', line 36 def initialize(file, opts={:timeout => -1}) @file = file @held = false @timeout = opts[:timeout] @release_fxn = opts[:release_fxn] @description = opts[:desc] apply_lock end |
Instance Method Details
#apply_lock ⇒ Object
Applies the lock. Will sleep the thread for timeout
time trying to apply the lock before giving up and raising an error.
48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 |
# File 'lib/amp/repository/lock.rb', line 48 def apply_lock timeout = @timeout while true do begin # try_lock will raise of there is already a lock. try_lock return true rescue LockHeld => e # We'll put up with this exception for @timeout times, then give up. if timeout != 0 sleep(1) timeout > 0 && timeout -= 1 next end # Timeout's up? Raise an exception. raise LockHeld.new(Errno::ETIMEDOUT::Errno, e.filename, @desc, e.locker) end end end |
#make_a_lock(file, info) ⇒ Object
Creates a lock at the given location, with info about the locking process. Uses a symlink if possible, because even over NFS, creating a symlink is atomic. Nice. Otherwise, it will call make_a_lock_in_file on inferior OS’s (cough windows cough) and put the data in there.
The symlink is actually a non-working symlink - it points the filename (such as “hglock”) to the data, even though the data is not an actual file. So hglock -> “medgar:25043” is a sort-of possible lock this method would create.
103 104 105 106 107 108 109 110 111 |
# File 'lib/amp/repository/lock.rb', line 103 def make_a_lock(file, info) begin File.symlink(info, file) rescue Errno::EEXIST raise rescue make_a_lock_in_file(file, info) end end |
#make_a_lock_in_file(file, info) ⇒ Object
Creates a lock at the given location, storing the info about the locking process in an actual lock file. These locks are not preferred, because symlinks are atomic even over NFS. Anyway, very simple. Create the file, write in the info, close ‘er up. That’s 1 line in ruby, folks.
122 123 124 |
# File 'lib/amp/repository/lock.rb', line 122 def make_a_lock_in_file(file, info) File.open(file, "w+") {|out| out.write info } end |
#read_lock(file) ⇒ String
Reads in the data associated with a lock file.
131 132 133 134 135 136 137 |
# File 'lib/amp/repository/lock.rb', line 131 def read_lock(file) begin return File.readlink(file) rescue Errno::EINVAL, Errno::ENOSYS return File.read(file) end end |
#release ⇒ Object
Releases the lock, signalling that it is now safe to modify the directory in which the lock is found.
195 196 197 198 199 200 201 202 |
# File 'lib/amp/repository/lock.rb', line 195 def release if @held @held = false @release_fxn.call if @release_fxn File.unlink(@file) rescue "" end end |
#test_lock ⇒ Object
Text from mercurial code:
return id of locker if lock is valid, else None.
If old-style lock, we cannot tell what machine locker is on. with new-style lock, if locker is on this machine, we can see if locker is alive. If locker is on this machine but not alive, we can safely break lock.
The lock file is only deleted when None is returned.
170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 |
# File 'lib/amp/repository/lock.rb', line 170 def test_lock locker = read_lock(@file) host, pid = locker.split(":", 1) return locker if pid.nil? || host != @@host pid = pid.to_i return locker if pid == 0 return locker if test_pid pid # if locker dead, break lock. must do this with another lock # held, or can race and break valid lock. begin the_lock = Lock.new(@file + ".break") the_lock.try_lock File.unlink(@file) the_lock.release rescue LockError return locker end end |
#test_pid(pid) ⇒ Boolean
Checks to see if there is a process running with id pid
.
144 145 146 147 148 149 150 151 152 153 154 155 156 |
# File 'lib/amp/repository/lock.rb', line 144 def test_pid(pid) return true if Platform::OS == :vms begin # Doesn't actually kill it Process.kill(0, pid) true rescue Errno::ESRCH::Errno true rescue false end end |
#try_lock ⇒ Object
Attempts to apply the lock. Raises if unsuccessful. Contains the logic for actually naming the lock.
71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 |
# File 'lib/amp/repository/lock.rb', line 71 def try_lock if @@host.nil? @@host = Socket.gethostname end lockname = "#{@@host}:#{Process.pid}" while !@held begin make_a_lock(@file, lockname) @held = true rescue Errno::EEXIST locker = test_lock unless locker.nil? raise LockHeld.new(Errno::EAGAIN::Errno, @file, @desc, locker) end rescue SystemCallError => e raise LockUnavailable.new(e.errno, e.to_s, @file, @desc) end end end |