Patchwork [7,of,7] lock: recognize parent locks while acquiring

login
register
mail settings
Submitter Siddharth Agarwal
Date Sept. 25, 2015, 7 p.m.
Message ID <21180f5347ad9e804765.1443207641@dev6666.prn1.facebook.com>
Download mbox | patch
Permalink /patch/10642/
State Accepted
Headers show

Comments

Siddharth Agarwal - Sept. 25, 2015, 7 p.m.
# HG changeset patch
# User Siddharth Agarwal <sid0@fb.com>
# Date 1443157675 25200
#      Thu Sep 24 22:07:55 2015 -0700
# Node ID 21180f5347ad9e804765cd92333627330ce89739
# Parent  5049d8ab47a936227186493f7de27a2bea35d627
lock: recognize parent locks while acquiring

This is part of a series that will allow locks to be inherited by subprocesses
in limited circumstances. This patch enables the logic introduced in previous
patches.
Pierre-Yves David - Sept. 28, 2015, 9:32 a.m.
On 09/25/2015 12:00 PM, Siddharth Agarwal wrote:
> # HG changeset patch
> # User Siddharth Agarwal <sid0@fb.com>
> # Date 1443157675 25200
> #      Thu Sep 24 22:07:55 2015 -0700
> # Node ID 21180f5347ad9e804765cd92333627330ce89739
> # Parent  5049d8ab47a936227186493f7de27a2bea35d627
> lock: recognize parent locks while acquiring

This series is pushed to the clowncopter.

Patch

diff --git a/mercurial/lock.py b/mercurial/lock.py
--- a/mercurial/lock.py
+++ b/mercurial/lock.py
@@ -102,7 +102,16 @@  class lock(object):
                 self.held = 1
             except (OSError, IOError) as why:
                 if why.errno == errno.EEXIST:
-                    locker = self.testlock()
+                    locker = self._readlock()
+                    # special case where a parent process holds the lock -- this
+                    # is different from the pid being different because we do
+                    # want the unlock and postrelease functions to be called,
+                    # but the lockfile to not be removed.
+                    if locker == self.parentlock:
+                        self._parentheld = True
+                        self.held = 1
+                        return
+                    locker = self._testlock(locker)
                     if locker is not None:
                         raise error.LockHeld(errno.EAGAIN,
                                              self.vfs.join(self.f), self.desc,
diff --git a/tests/test-lock.py b/tests/test-lock.py
--- a/tests/test-lock.py
+++ b/tests/test-lock.py
@@ -151,5 +151,106 @@  class testlock(unittest.TestCase):
         state.assertpostreleasecalled(True)
         state.assertlockexists(False)
 
+    def testinheritlock(self):
+        d = tempfile.mkdtemp(dir=os.getcwd())
+        parentstate = teststate(self, d)
+        parentlock = parentstate.makelock()
+        parentstate.assertacquirecalled(True)
+
+        # set up lock inheritance
+        lockname = parentlock.prepinherit()
+        parentstate.assertreleasecalled(True)
+        parentstate.assertpostreleasecalled(False)
+        parentstate.assertlockexists(True)
+
+        childstate = teststate(self, d, pidoffset=1)
+        childlock = childstate.makelock(parentlock=lockname)
+        childstate.assertacquirecalled(True)
+
+        # release the child lock -- the lock file should still exist on disk
+        childlock.release()
+        childstate.assertreleasecalled(True)
+        childstate.assertpostreleasecalled(True)
+        childstate.assertlockexists(True)
+
+        parentstate.resetacquirefn()
+        parentlock.reacquire()
+        parentstate.assertacquirecalled(True)
+
+        parentlock.release()
+        parentstate.assertreleasecalled(True)
+        parentstate.assertpostreleasecalled(True)
+        parentstate.assertlockexists(False)
+
+    def testmultilock(self):
+        d = tempfile.mkdtemp(dir=os.getcwd())
+        state0 = teststate(self, d)
+        lock0 = state0.makelock()
+        state0.assertacquirecalled(True)
+
+        lock0name = lock0.prepinherit()
+        state0.assertreleasecalled(True)
+        state0.assertpostreleasecalled(False)
+        state0.assertlockexists(True)
+
+        state1 = teststate(self, d, pidoffset=1)
+        lock1 = state1.makelock(parentlock=lock0name)
+        state1.assertacquirecalled(True)
+
+        # from within lock1, acquire another lock
+        lock1name = lock1.prepinherit()
+        # since the file on disk is lock0's this should have the same name
+        self.assertEqual(lock0name, lock1name)
+
+        state2 = teststate(self, d, pidoffset=2)
+        lock2 = state2.makelock(parentlock=lock1name)
+        state2.assertacquirecalled(True)
+
+        lock2.release()
+        state2.assertreleasecalled(True)
+        state2.assertpostreleasecalled(True)
+        state2.assertlockexists(True)
+
+        state1.resetacquirefn()
+        lock1.reacquire()
+        state1.assertacquirecalled(True)
+
+        lock1.release()
+        state1.assertreleasecalled(True)
+        state1.assertpostreleasecalled(True)
+        state1.assertlockexists(True)
+
+        lock0.reacquire()
+        lock0.release()
+
+    def testinheritlockfork(self):
+        d = tempfile.mkdtemp(dir=os.getcwd())
+        parentstate = teststate(self, d)
+        parentlock = parentstate.makelock()
+        parentstate.assertacquirecalled(True)
+
+        # set up lock inheritance
+        lockname = parentlock.prepinherit()
+        childstate = teststate(self, d, pidoffset=1)
+        childlock = childstate.makelock(parentlock=lockname)
+        childstate.assertacquirecalled(True)
+
+        # fork the child lock
+        forkchildlock = copy.deepcopy(childlock)
+        forkchildlock._pidoffset += 1
+        forkchildlock.release()
+        childstate.assertreleasecalled(False)
+        childstate.assertpostreleasecalled(False)
+        childstate.assertlockexists(True)
+
+        # release the child lock
+        childlock.release()
+        childstate.assertreleasecalled(True)
+        childstate.assertpostreleasecalled(True)
+        childstate.assertlockexists(True)
+
+        parentlock.reacquire()
+        parentlock.release()
+
 if __name__ == '__main__':
     silenttestrunner.main(__name__)