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.
--- 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):