lock.py
127 lines
| 3.8 KiB
| text/x-python
|
PythonLexer
/ mercurial / lock.py
|
r161 | # lock.py - simple locking scheme for mercurial | ||
# | ||||
|
r2859 | # Copyright 2005, 2006 Matt Mackall <mpm@selenic.com> | ||
|
r161 | # | ||
|
r8225 | # This software may be used and distributed according to the terms of the | ||
# GNU General Public License version 2, incorporated herein by reference. | ||||
|
r161 | |||
|
r7640 | import errno, os, socket, time, util, error | ||
|
r8113 | import warnings | ||
|
r161 | |||
|
r1559 | class lock(object): | ||
|
r1877 | # 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 | ||||
|
r4947 | _host = None | ||
|
r2016 | def __init__(self, file, timeout=-1, releasefn=None, desc=None): | ||
|
r161 | self.f = file | ||
self.held = 0 | ||||
|
r1787 | self.timeout = timeout | ||
|
r1530 | self.releasefn = releasefn | ||
|
r2016 | self.desc = desc | ||
|
r161 | self.lock() | ||
def __del__(self): | ||||
|
r8108 | if self.held: | ||
|
r8113 | warnings.warn("use lock.release instead of del lock", | ||
category=DeprecationWarning, | ||||
stacklevel=2) | ||||
|
r8108 | # ensure the lock will be removed | ||
# even if recursive locking did occur | ||||
self.held = 1 | ||||
|
r161 | self.release() | ||
def lock(self): | ||||
|
r1787 | timeout = self.timeout | ||
|
r161 | while 1: | ||
try: | ||||
self.trylock() | ||||
return 1 | ||||
|
r7640 | except error.LockHeld, inst: | ||
|
r1787 | if timeout != 0: | ||
|
r161 | time.sleep(1) | ||
|
r1787 | if timeout > 0: | ||
timeout -= 1 | ||||
|
r161 | continue | ||
|
r7640 | raise error.LockHeld(errno.ETIMEDOUT, inst.filename, self.desc, | ||
inst.locker) | ||||
|
r515 | |||
|
r161 | def trylock(self): | ||
|
r8108 | if self.held: | ||
self.held += 1 | ||||
return | ||||
|
r4947 | if lock._host is None: | ||
lock._host = socket.gethostname() | ||||
|
r4959 | lockname = '%s:%s' % (lock._host, os.getpid()) | ||
|
r1877 | while not self.held: | ||
try: | ||||
|
r4947 | util.makelock(lockname, self.f) | ||
|
r1877 | self.held = 1 | ||
except (OSError, IOError), why: | ||||
if why.errno == errno.EEXIST: | ||||
locker = self.testlock() | ||||
|
r3686 | if locker is not None: | ||
|
r7640 | raise error.LockHeld(errno.EAGAIN, self.f, self.desc, | ||
locker) | ||||
|
r1877 | else: | ||
|
r7640 | raise error.LockUnavailable(why.errno, why.strerror, | ||
why.filename, self.desc) | ||||
|
r1877 | |||
def testlock(self): | ||||
|
r3686 | """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. | ||||
""" | ||||
|
r1877 | locker = util.readlock(self.f) | ||
|
r2579 | try: | ||
host, pid = locker.split(":", 1) | ||||
except ValueError: | ||||
|
r1877 | return locker | ||
|
r4947 | if host != lock._host: | ||
|
r1877 | return locker | ||
|
r161 | try: | ||
|
r2579 | pid = int(pid) | ||
|
r1877 | except: | ||
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') | ||||
l.trylock() | ||||
os.unlink(self.f) | ||||
l.release() | ||||
|
r7640 | except error.LockError: | ||
|
r1877 | return locker | ||
|
r161 | |||
def release(self): | ||||
|
r8108 | if self.held > 1: | ||
self.held -= 1 | ||||
elif self.held is 1: | ||||
|
r161 | self.held = 0 | ||
|
r1530 | if self.releasefn: | ||
self.releasefn() | ||||
|
r503 | try: | ||
os.unlink(self.f) | ||||
except: pass | ||||
|
r161 | |||
|
r8108 | def release(*locks): | ||
for lock in locks: | ||||
if lock is not None: | ||||
lock.release() | ||||