mirror of
https://github.com/facebook/sapling.git
synced 2024-10-10 08:47:12 +03:00
7063833db6
Suppose the following scenario: 1. Process A takes the lock (e.g. on commit). 2. Process B wants to grab the lock. Since lock file exists the exception is raised. In the catch block the testlock function is called. 3. Process A releases the lock. 4. Process B tries to read the lock file as a part of testlock function. This results in OSError (ENOENT) and since we're not inside the exception handler function this is propagated and aborts the whole operation. To fix this we now check in testlock function whether lock file actually exists and if not (i.e. if readlock fails) we just return.
149 lines
4.7 KiB
Python
149 lines
4.7 KiB
Python
# lock.py - simple advisory locking scheme for mercurial
|
|
#
|
|
# Copyright 2005, 2006 Matt Mackall <mpm@selenic.com>
|
|
#
|
|
# This software may be used and distributed according to the terms of the
|
|
# GNU General Public License version 2 or any later version.
|
|
|
|
import util, error
|
|
import errno, os, socket, time
|
|
import warnings
|
|
|
|
class lock(object):
|
|
'''An advisory lock held by one process to control access to a set
|
|
of files. Non-cooperating processes or incorrectly written scripts
|
|
can ignore Mercurial's locking scheme and stomp all over the
|
|
repository, so don't do that.
|
|
|
|
Typically used via localrepository.lock() to lock the repository
|
|
store (.hg/store/) or localrepository.wlock() to lock everything
|
|
else under .hg/.'''
|
|
|
|
# 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
|
|
|
|
_host = None
|
|
|
|
def __init__(self, file, timeout=-1, releasefn=None, desc=None):
|
|
self.f = file
|
|
self.held = 0
|
|
self.timeout = timeout
|
|
self.releasefn = releasefn
|
|
self.desc = desc
|
|
self.postrelease = []
|
|
self.lock()
|
|
|
|
def __del__(self):
|
|
if self.held:
|
|
warnings.warn("use lock.release instead of del lock",
|
|
category=DeprecationWarning,
|
|
stacklevel=2)
|
|
|
|
# ensure the lock will be removed
|
|
# even if recursive locking did occur
|
|
self.held = 1
|
|
|
|
self.release()
|
|
|
|
def lock(self):
|
|
timeout = self.timeout
|
|
while True:
|
|
try:
|
|
self.trylock()
|
|
return 1
|
|
except error.LockHeld, inst:
|
|
if timeout != 0:
|
|
time.sleep(1)
|
|
if timeout > 0:
|
|
timeout -= 1
|
|
continue
|
|
raise error.LockHeld(errno.ETIMEDOUT, inst.filename, self.desc,
|
|
inst.locker)
|
|
|
|
def trylock(self):
|
|
if self.held:
|
|
self.held += 1
|
|
return
|
|
if lock._host is None:
|
|
lock._host = socket.gethostname()
|
|
lockname = '%s:%s' % (lock._host, os.getpid())
|
|
while not self.held:
|
|
try:
|
|
util.makelock(lockname, self.f)
|
|
self.held = 1
|
|
except (OSError, IOError), why:
|
|
if why.errno == errno.EEXIST:
|
|
locker = self.testlock()
|
|
if locker is not None:
|
|
raise error.LockHeld(errno.EAGAIN, self.f, self.desc,
|
|
locker)
|
|
else:
|
|
raise error.LockUnavailable(why.errno, why.strerror,
|
|
why.filename, self.desc)
|
|
|
|
def testlock(self):
|
|
"""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.
|
|
|
|
"""
|
|
try:
|
|
locker = util.readlock(self.f)
|
|
except OSError, why:
|
|
if why.errno == errno.ENOENT:
|
|
return None
|
|
raise
|
|
try:
|
|
host, pid = locker.split(":", 1)
|
|
except ValueError:
|
|
return locker
|
|
if host != lock._host:
|
|
return locker
|
|
try:
|
|
pid = int(pid)
|
|
except ValueError:
|
|
return locker
|
|
if util.testpid(pid):
|
|
return locker
|
|
# if locker dead, break lock. must do this with another lock
|
|
# held, or can race and break valid lock.
|
|
try:
|
|
l = lock(self.f + '.break', timeout=0)
|
|
util.unlink(self.f)
|
|
l.release()
|
|
except error.LockError:
|
|
return locker
|
|
|
|
def release(self):
|
|
"""release the lock and execute callback function if any
|
|
|
|
If the lock has been acquired multiple times, the actual release is
|
|
delayed to the last release call."""
|
|
if self.held > 1:
|
|
self.held -= 1
|
|
elif self.held == 1:
|
|
self.held = 0
|
|
if self.releasefn:
|
|
self.releasefn()
|
|
try:
|
|
util.unlink(self.f)
|
|
except OSError:
|
|
pass
|
|
for callback in self.postrelease:
|
|
callback()
|
|
|
|
def release(*locks):
|
|
for lock in locks:
|
|
if lock is not None:
|
|
lock.release()
|