mercurial/lock.py
changeset 1877 d314a89fa4f1
parent 1836 cd5c1db2132a
child 2016 ff5c9a92f556
--- 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: