lock.py
108 lines
| 3.1 KiB
| text/x-python
|
PythonLexer
/ mercurial / lock.py
mpm@selenic.com
|
r161 | # lock.py - simple locking scheme for mercurial | ||
# | ||||
# Copyright 2005 Matt Mackall <mpm@selenic.com> | ||||
# | ||||
# This software may be used and distributed according to the terms | ||||
# of the GNU General Public License, incorporated herein by reference. | ||||
Vadim Gelfer
|
r1836 | from demandload import * | ||
Vadim Gelfer
|
r1877 | demandload(globals(), 'errno os socket time util') | ||
mpm@selenic.com
|
r161 | |||
Benoit Boissinot
|
r1753 | class LockException(Exception): | ||
pass | ||||
class LockHeld(LockException): | ||||
pass | ||||
class LockUnavailable(LockException): | ||||
mpm@selenic.com
|
r161 | pass | ||
Eric Hopper
|
r1559 | class lock(object): | ||
Vadim Gelfer
|
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 | ||||
Benoit Boissinot
|
r1787 | def __init__(self, file, timeout=-1, releasefn=None): | ||
mpm@selenic.com
|
r161 | self.f = file | ||
self.held = 0 | ||||
Benoit Boissinot
|
r1787 | self.timeout = timeout | ||
Benoit Boissinot
|
r1530 | self.releasefn = releasefn | ||
Vadim Gelfer
|
r1877 | self.id = None | ||
self.host = None | ||||
self.pid = None | ||||
mpm@selenic.com
|
r161 | self.lock() | ||
def __del__(self): | ||||
self.release() | ||||
def lock(self): | ||||
Benoit Boissinot
|
r1787 | timeout = self.timeout | ||
mpm@selenic.com
|
r161 | while 1: | ||
try: | ||||
self.trylock() | ||||
return 1 | ||||
except LockHeld, inst: | ||||
Benoit Boissinot
|
r1787 | if timeout != 0: | ||
mpm@selenic.com
|
r161 | time.sleep(1) | ||
Benoit Boissinot
|
r1787 | if timeout > 0: | ||
timeout -= 1 | ||||
mpm@selenic.com
|
r161 | continue | ||
raise inst | ||||
mpm@selenic.com
|
r515 | |||
mpm@selenic.com
|
r161 | def trylock(self): | ||
Vadim Gelfer
|
r1877 | if self.id is None: | ||
self.host = socket.gethostname() | ||||
self.pid = os.getpid() | ||||
self.id = '%s:%s' % (self.host, self.pid) | ||||
while not self.held: | ||||
try: | ||||
util.makelock(self.id, self.f) | ||||
self.held = 1 | ||||
except (OSError, IOError), why: | ||||
if why.errno == errno.EEXIST: | ||||
locker = self.testlock() | ||||
if locker: | ||||
raise LockHeld(locker) | ||||
else: | ||||
raise LockUnavailable(why) | ||||
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. | ||||
locker = util.readlock(self.f) | ||||
c = locker.find(':') | ||||
if c == -1: | ||||
return locker | ||||
host = locker[:c] | ||||
if host != self.host: | ||||
return locker | ||||
mpm@selenic.com
|
r161 | try: | ||
Vadim Gelfer
|
r1877 | pid = int(locker[c+1:]) | ||
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() | ||||
except (LockHeld, LockUnavailable): | ||||
return locker | ||||
mpm@selenic.com
|
r161 | |||
def release(self): | ||||
if self.held: | ||||
self.held = 0 | ||||
Benoit Boissinot
|
r1530 | if self.releasefn: | ||
self.releasefn() | ||||
mpm@selenic.com
|
r503 | try: | ||
os.unlink(self.f) | ||||
except: pass | ||||
mpm@selenic.com
|
r161 | |||