# HG changeset patch # User Vadim Gelfer # Date 1142008291 28800 # Node ID d314a89fa4f16b7a9b24dc999145d98a878f7ca9 # Parent 2e0fd78587bdb1ecaf56ef9aaf428d2b7a271217 change lock format to let us detect and break stale locks. old style: symlink to pid new style: symlink to hostname:pid if lock code finds new-style lock, it breaks lock if locking pid is on same machine and pid is not alive. otherwise, lock is left alone. this makes locking code safe with old-style locks and with locks on other machines. new code makes server part of mercurial more robust in case machine crashes, power fails, or crazy user does kill -9. diff --git a/mercurial/lock.py b/mercurial/lock.py --- a/mercurial/lock.py +++ b/mercurial/lock.py @@ -6,7 +6,7 @@ # of the GNU General Public License, incorporated herein by reference. from demandload import * -demandload(globals(), 'errno os time util') +demandload(globals(), 'errno os socket time util') class LockException(Exception): pass @@ -16,11 +16,22 @@ 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): @@ -41,15 +52,50 @@ class lock(object): raise inst def trylock(self): - pid = os.getpid() + 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: - util.makelock(str(pid), self.f) - self.held = 1 - except (OSError, IOError), why: - if why.errno == errno.EEXIST: - raise LockHeld(util.readlock(self.f)) - else: - raise LockUnavailable(why) + 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: diff --git a/mercurial/util.py b/mercurial/util.py --- a/mercurial/util.py +++ b/mercurial/util.py @@ -499,7 +499,7 @@ if os.name == 'nt': return pf try: # ActivePython can create hard links using win32file module - import win32file + import win32api, win32con, win32file def os_link(src, dst): # NB will only succeed on NTFS win32file.CreateHardLink(dst, src) @@ -516,8 +516,18 @@ if os.name == 'nt': except: return os.stat(pathname).st_nlink + def testpid(pid): + '''return False if pid is dead, True if running or not known''' + try: + win32api.OpenProcess(win32con.PROCESS_QUERY_INFORMATION, + False, pid) + except: + return True + except ImportError: - pass + def testpid(pid): + '''return False if pid dead, True if running or not known''' + return True def is_exec(f, last): return last @@ -614,6 +624,14 @@ else: else: raise + def testpid(pid): + '''return False if pid dead, True if running or not sure''' + try: + os.kill(pid, 0) + return True + except OSError, inst: + return inst.errno != errno.ESRCH + def explain_exit(code): """return a 2-tuple (desc, code) describing a process's status""" if os.WIFEXITED(code):