# 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.
from demandload import *
demandload(globals(), 'errno os socket time util')
class LockException(Exception):
pass
class LockHeld(LockException):
pass
class LockUnavailable(LockException):
pass
class lock(object):
# 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
def __init__(self, file, timeout=-1, releasefn=None):
self.f = file
self.held = 0
self.timeout = timeout
self.releasefn = releasefn
self.id = None
self.host = None
self.pid = None
self.lock()
def __del__(self):
self.release()
def lock(self):
timeout = self.timeout
while 1:
try:
self.trylock()
return 1
except LockHeld, inst:
if timeout != 0:
time.sleep(1)
if timeout > 0:
timeout -= 1
continue
raise inst
def trylock(self):
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
try:
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
def release(self):
if self.held:
self.held = 0
if self.releasefn:
self.releasefn()
try:
os.unlink(self.f)
except: pass