Mercurial > hg-stable
changeset 1877:d314a89fa4f1
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.
author | Vadim Gelfer <vadim.gelfer@gmail.com> |
---|---|
date | Fri, 10 Mar 2006 08:31:31 -0800 |
parents | 2e0fd78587bd |
children | a5c46cff620f |
files | mercurial/lock.py mercurial/util.py |
diffstat | 2 files changed, 75 insertions(+), 11 deletions(-) [+] |
line wrap: on
line diff
--- a/mercurial/lock.py Fri Mar 10 11:34:02 2006 +0100 +++ b/mercurial/lock.py Fri Mar 10 08:31:31 2006 -0800 @@ -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 @@ 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 @@ 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:
--- a/mercurial/util.py Fri Mar 10 11:34:02 2006 +0100 +++ b/mercurial/util.py Fri Mar 10 08:31:31 2006 -0800 @@ -499,7 +499,7 @@ 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 @@ 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: 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):