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__)