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.
--- a/mercurial/lock.py Thu Sep 24 22:00:51 2015 -0700
+++ b/mercurial/lock.py Thu Sep 24 22:07:55 2015 -0700
@@ -102,7 +102,16 @@
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,
--- a/tests/test-lock.py Thu Sep 24 22:00:51 2015 -0700
+++ b/tests/test-lock.py Thu Sep 24 22:07:55 2015 -0700
@@ -151,5 +151,106 @@
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__)