2015-09-18 01:38:00 +03:00
|
|
|
from __future__ import absolute_import
|
|
|
|
|
2015-09-25 08:00:51 +03:00
|
|
|
import copy
|
lock: avoid unintentional lock acquisition at failure of readlock
Acquiring lock by vfs.makelock() and getting lock holder (aka
"locker") information by vfs.readlock() aren't atomic action.
Therefore, failure of the former doesn't ensure success of the latter.
Before this patch, lock is unintentionally acquired, if ENOENT
causes failure of vfs.readlock() while 5 times retrying, because
lock._trylock() returns to caller silently after retrying, and
lock.lock() assumes that lock._trylock() returns successfully only if
lock is acquired.
In this case, lock symlink (or file) isn't created, even though lock
is treated as acquired in memory.
To avoid this issue, this patch makes lock._trylock() raise
LockHeld(EAGAIN) at the end of it, if lock isn't acquired while
retrying.
An empty "locker" meaning "busy for frequent lock/unlock by many
processes" might appear in an abortion message, if lock acquisition
fails. Therefore, this patch also does:
- use '%r' to increase visibility of "locker", even if it is empty
- show hint message to explain what empty "locker" means
2017-05-01 13:59:13 +03:00
|
|
|
import errno
|
2015-09-18 01:38:00 +03:00
|
|
|
import os
|
|
|
|
import silenttestrunner
|
|
|
|
import tempfile
|
2015-09-25 08:00:51 +03:00
|
|
|
import types
|
2015-09-18 01:38:00 +03:00
|
|
|
import unittest
|
|
|
|
|
|
|
|
from mercurial import (
|
2015-10-06 23:13:31 +03:00
|
|
|
error,
|
2015-09-18 01:38:00 +03:00
|
|
|
lock,
|
2017-03-02 15:33:02 +03:00
|
|
|
vfs as vfsmod,
|
2015-09-18 01:38:00 +03:00
|
|
|
)
|
|
|
|
|
|
|
|
testlockname = 'testlock'
|
|
|
|
|
2015-09-25 08:00:51 +03:00
|
|
|
# work around http://bugs.python.org/issue1515
|
|
|
|
if types.MethodType not in copy._deepcopy_dispatch:
|
|
|
|
def _deepcopy_method(x, memo):
|
|
|
|
return type(x)(x.im_func, copy.deepcopy(x.im_self, memo), x.im_class)
|
|
|
|
copy._deepcopy_dispatch[types.MethodType] = _deepcopy_method
|
|
|
|
|
2015-09-25 06:22:59 +03:00
|
|
|
class lockwrapper(lock.lock):
|
|
|
|
def __init__(self, pidoffset, *args, **kwargs):
|
|
|
|
# lock.lock.__init__() calls lock(), so the pidoffset assignment needs
|
|
|
|
# to be earlier
|
|
|
|
self._pidoffset = pidoffset
|
|
|
|
super(lockwrapper, self).__init__(*args, **kwargs)
|
|
|
|
def _getpid(self):
|
2016-02-03 12:11:22 +03:00
|
|
|
return super(lockwrapper, self)._getpid() + self._pidoffset
|
2015-09-25 06:22:59 +03:00
|
|
|
|
2015-09-18 01:38:00 +03:00
|
|
|
class teststate(object):
|
2015-09-25 06:40:00 +03:00
|
|
|
def __init__(self, testcase, dir, pidoffset=0):
|
2015-09-18 01:38:00 +03:00
|
|
|
self._testcase = testcase
|
2015-09-23 00:09:42 +03:00
|
|
|
self._acquirecalled = False
|
2015-09-18 01:38:00 +03:00
|
|
|
self._releasecalled = False
|
|
|
|
self._postreleasecalled = False
|
2017-03-02 15:33:02 +03:00
|
|
|
self.vfs = vfsmod.vfs(dir, audit=False)
|
2015-09-25 06:40:00 +03:00
|
|
|
self._pidoffset = pidoffset
|
2015-09-18 01:38:00 +03:00
|
|
|
|
|
|
|
def makelock(self, *args, **kwargs):
|
2015-09-25 06:40:00 +03:00
|
|
|
l = lockwrapper(self._pidoffset, self.vfs, testlockname,
|
|
|
|
releasefn=self.releasefn, acquirefn=self.acquirefn,
|
|
|
|
*args, **kwargs)
|
2015-09-18 01:38:00 +03:00
|
|
|
l.postrelease.append(self.postreleasefn)
|
|
|
|
return l
|
|
|
|
|
2015-09-23 00:09:42 +03:00
|
|
|
def acquirefn(self):
|
|
|
|
self._acquirecalled = True
|
|
|
|
|
2015-09-18 01:38:00 +03:00
|
|
|
def releasefn(self):
|
|
|
|
self._releasecalled = True
|
|
|
|
|
|
|
|
def postreleasefn(self):
|
|
|
|
self._postreleasecalled = True
|
|
|
|
|
2015-09-23 00:09:42 +03:00
|
|
|
def assertacquirecalled(self, called):
|
|
|
|
self._testcase.assertEqual(
|
|
|
|
self._acquirecalled, called,
|
|
|
|
'expected acquire to be %s but was actually %s' % (
|
|
|
|
self._tocalled(called),
|
|
|
|
self._tocalled(self._acquirecalled),
|
|
|
|
))
|
|
|
|
|
|
|
|
def resetacquirefn(self):
|
|
|
|
self._acquirecalled = False
|
|
|
|
|
2015-09-18 01:38:00 +03:00
|
|
|
def assertreleasecalled(self, called):
|
|
|
|
self._testcase.assertEqual(
|
|
|
|
self._releasecalled, called,
|
|
|
|
'expected release to be %s but was actually %s' % (
|
|
|
|
self._tocalled(called),
|
|
|
|
self._tocalled(self._releasecalled),
|
|
|
|
))
|
|
|
|
|
|
|
|
def assertpostreleasecalled(self, called):
|
|
|
|
self._testcase.assertEqual(
|
|
|
|
self._postreleasecalled, called,
|
|
|
|
'expected postrelease to be %s but was actually %s' % (
|
|
|
|
self._tocalled(called),
|
|
|
|
self._tocalled(self._postreleasecalled),
|
|
|
|
))
|
|
|
|
|
|
|
|
def assertlockexists(self, exists):
|
|
|
|
actual = self.vfs.lexists(testlockname)
|
|
|
|
self._testcase.assertEqual(
|
|
|
|
actual, exists,
|
|
|
|
'expected lock to %s but actually did %s' % (
|
|
|
|
self._toexists(exists),
|
|
|
|
self._toexists(actual),
|
|
|
|
))
|
|
|
|
|
|
|
|
def _tocalled(self, called):
|
|
|
|
if called:
|
|
|
|
return 'called'
|
|
|
|
else:
|
|
|
|
return 'not called'
|
|
|
|
|
|
|
|
def _toexists(self, exists):
|
|
|
|
if exists:
|
2015-09-25 03:33:13 +03:00
|
|
|
return 'exist'
|
2015-09-18 01:38:00 +03:00
|
|
|
else:
|
2015-09-25 03:33:13 +03:00
|
|
|
return 'not exist'
|
2015-09-18 01:38:00 +03:00
|
|
|
|
|
|
|
class testlock(unittest.TestCase):
|
|
|
|
def testlock(self):
|
2015-09-25 05:52:34 +03:00
|
|
|
state = teststate(self, tempfile.mkdtemp(dir=os.getcwd()))
|
2015-09-18 01:38:00 +03:00
|
|
|
lock = state.makelock()
|
2015-09-23 00:09:42 +03:00
|
|
|
state.assertacquirecalled(True)
|
2015-09-18 01:38:00 +03:00
|
|
|
lock.release()
|
|
|
|
state.assertreleasecalled(True)
|
|
|
|
state.assertpostreleasecalled(True)
|
|
|
|
state.assertlockexists(False)
|
|
|
|
|
|
|
|
def testrecursivelock(self):
|
2015-09-25 05:52:34 +03:00
|
|
|
state = teststate(self, tempfile.mkdtemp(dir=os.getcwd()))
|
2015-09-18 01:38:00 +03:00
|
|
|
lock = state.makelock()
|
2015-09-23 00:09:42 +03:00
|
|
|
state.assertacquirecalled(True)
|
|
|
|
|
|
|
|
state.resetacquirefn()
|
2015-09-18 01:38:00 +03:00
|
|
|
lock.lock()
|
2015-09-23 00:09:42 +03:00
|
|
|
# recursive lock should not call acquirefn again
|
|
|
|
state.assertacquirecalled(False)
|
|
|
|
|
2015-09-18 01:38:00 +03:00
|
|
|
lock.release() # brings lock refcount down from 2 to 1
|
|
|
|
state.assertreleasecalled(False)
|
|
|
|
state.assertpostreleasecalled(False)
|
|
|
|
state.assertlockexists(True)
|
|
|
|
|
|
|
|
lock.release() # releases the lock
|
|
|
|
state.assertreleasecalled(True)
|
|
|
|
state.assertpostreleasecalled(True)
|
|
|
|
state.assertlockexists(False)
|
|
|
|
|
|
|
|
def testlockfork(self):
|
2015-09-25 05:52:34 +03:00
|
|
|
state = teststate(self, tempfile.mkdtemp(dir=os.getcwd()))
|
2015-09-18 01:38:00 +03:00
|
|
|
lock = state.makelock()
|
2015-09-23 00:09:42 +03:00
|
|
|
state.assertacquirecalled(True)
|
2015-09-25 08:00:51 +03:00
|
|
|
|
2015-09-18 01:38:00 +03:00
|
|
|
# fake a fork
|
2015-09-25 08:00:51 +03:00
|
|
|
forklock = copy.deepcopy(lock)
|
|
|
|
forklock._pidoffset = 1
|
|
|
|
forklock.release()
|
2015-09-18 01:38:00 +03:00
|
|
|
state.assertreleasecalled(False)
|
|
|
|
state.assertpostreleasecalled(False)
|
|
|
|
state.assertlockexists(True)
|
|
|
|
|
|
|
|
# release the actual lock
|
|
|
|
lock.release()
|
|
|
|
state.assertreleasecalled(True)
|
|
|
|
state.assertpostreleasecalled(True)
|
|
|
|
state.assertlockexists(False)
|
|
|
|
|
2015-09-25 08:07:55 +03:00
|
|
|
def testinheritlock(self):
|
|
|
|
d = tempfile.mkdtemp(dir=os.getcwd())
|
|
|
|
parentstate = teststate(self, d)
|
|
|
|
parentlock = parentstate.makelock()
|
|
|
|
parentstate.assertacquirecalled(True)
|
|
|
|
|
|
|
|
# set up lock inheritance
|
2015-10-05 06:02:50 +03:00
|
|
|
with parentlock.inherit() as lockname:
|
|
|
|
parentstate.assertreleasecalled(True)
|
|
|
|
parentstate.assertpostreleasecalled(False)
|
|
|
|
parentstate.assertlockexists(True)
|
|
|
|
|
|
|
|
childstate = teststate(self, d, pidoffset=1)
|
|
|
|
childlock = childstate.makelock(parentlock=lockname)
|
|
|
|
childstate.assertacquirecalled(True)
|
2015-09-25 08:07:55 +03:00
|
|
|
|
2015-10-05 06:02:50 +03:00
|
|
|
childlock.release()
|
|
|
|
childstate.assertreleasecalled(True)
|
2015-10-05 06:04:44 +03:00
|
|
|
childstate.assertpostreleasecalled(False)
|
2015-10-05 06:02:50 +03:00
|
|
|
childstate.assertlockexists(True)
|
2015-09-25 08:07:55 +03:00
|
|
|
|
2015-10-05 06:02:50 +03:00
|
|
|
parentstate.resetacquirefn()
|
2015-09-25 08:07:55 +03:00
|
|
|
|
|
|
|
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)
|
|
|
|
|
2015-10-05 06:02:50 +03:00
|
|
|
with lock0.inherit() as lock0name:
|
|
|
|
state0.assertreleasecalled(True)
|
|
|
|
state0.assertpostreleasecalled(False)
|
|
|
|
state0.assertlockexists(True)
|
2015-09-25 08:07:55 +03:00
|
|
|
|
2015-10-05 06:02:50 +03:00
|
|
|
state1 = teststate(self, d, pidoffset=1)
|
|
|
|
lock1 = state1.makelock(parentlock=lock0name)
|
|
|
|
state1.assertacquirecalled(True)
|
2015-09-25 08:07:55 +03:00
|
|
|
|
2015-10-05 06:02:50 +03:00
|
|
|
# from within lock1, acquire another lock
|
|
|
|
with lock1.inherit() as lock1name:
|
|
|
|
# since the file on disk is lock0's this should have the same
|
|
|
|
# name
|
|
|
|
self.assertEqual(lock0name, lock1name)
|
2015-09-25 08:07:55 +03:00
|
|
|
|
2015-10-05 06:02:50 +03:00
|
|
|
state2 = teststate(self, d, pidoffset=2)
|
|
|
|
lock2 = state2.makelock(parentlock=lock1name)
|
|
|
|
state2.assertacquirecalled(True)
|
2015-09-25 08:07:55 +03:00
|
|
|
|
2015-10-05 06:02:50 +03:00
|
|
|
lock2.release()
|
|
|
|
state2.assertreleasecalled(True)
|
2015-10-05 06:04:44 +03:00
|
|
|
state2.assertpostreleasecalled(False)
|
2015-10-05 06:02:50 +03:00
|
|
|
state2.assertlockexists(True)
|
2015-09-25 08:07:55 +03:00
|
|
|
|
2015-10-05 06:02:50 +03:00
|
|
|
state1.resetacquirefn()
|
2015-09-25 08:07:55 +03:00
|
|
|
|
2015-10-05 06:02:50 +03:00
|
|
|
state1.assertacquirecalled(True)
|
|
|
|
|
|
|
|
lock1.release()
|
|
|
|
state1.assertreleasecalled(True)
|
2015-10-05 06:04:44 +03:00
|
|
|
state1.assertpostreleasecalled(False)
|
2015-10-05 06:02:50 +03:00
|
|
|
state1.assertlockexists(True)
|
2015-09-25 08:07:55 +03:00
|
|
|
|
|
|
|
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
|
2015-10-05 06:02:50 +03:00
|
|
|
with parentlock.inherit() as lockname:
|
|
|
|
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)
|
2015-10-05 06:04:44 +03:00
|
|
|
childstate.assertpostreleasecalled(False)
|
2015-10-05 06:02:50 +03:00
|
|
|
childstate.assertlockexists(True)
|
|
|
|
|
2015-09-25 08:07:55 +03:00
|
|
|
parentlock.release()
|
|
|
|
|
2015-10-06 23:13:31 +03:00
|
|
|
def testinheritcheck(self):
|
|
|
|
d = tempfile.mkdtemp(dir=os.getcwd())
|
|
|
|
state = teststate(self, d)
|
|
|
|
def check():
|
|
|
|
raise error.LockInheritanceContractViolation('check failed')
|
|
|
|
lock = state.makelock(inheritchecker=check)
|
|
|
|
state.assertacquirecalled(True)
|
|
|
|
|
2017-05-13 21:52:44 +03:00
|
|
|
with self.assertRaises(error.LockInheritanceContractViolation):
|
2015-10-06 23:19:05 +03:00
|
|
|
with lock.inherit():
|
2015-10-06 23:13:31 +03:00
|
|
|
pass
|
|
|
|
|
|
|
|
lock.release()
|
|
|
|
|
lock: avoid unintentional lock acquisition at failure of readlock
Acquiring lock by vfs.makelock() and getting lock holder (aka
"locker") information by vfs.readlock() aren't atomic action.
Therefore, failure of the former doesn't ensure success of the latter.
Before this patch, lock is unintentionally acquired, if ENOENT
causes failure of vfs.readlock() while 5 times retrying, because
lock._trylock() returns to caller silently after retrying, and
lock.lock() assumes that lock._trylock() returns successfully only if
lock is acquired.
In this case, lock symlink (or file) isn't created, even though lock
is treated as acquired in memory.
To avoid this issue, this patch makes lock._trylock() raise
LockHeld(EAGAIN) at the end of it, if lock isn't acquired while
retrying.
An empty "locker" meaning "busy for frequent lock/unlock by many
processes" might appear in an abortion message, if lock acquisition
fails. Therefore, this patch also does:
- use '%r' to increase visibility of "locker", even if it is empty
- show hint message to explain what empty "locker" means
2017-05-01 13:59:13 +03:00
|
|
|
def testfrequentlockunlock(self):
|
|
|
|
"""This tests whether lock acquisition fails as expected, even if
|
|
|
|
(1) lock can't be acquired (makelock fails by EEXIST), and
|
|
|
|
(2) locker info can't be read in (readlock fails by ENOENT) while
|
|
|
|
retrying 5 times.
|
|
|
|
"""
|
|
|
|
|
|
|
|
d = tempfile.mkdtemp(dir=os.getcwd())
|
|
|
|
state = teststate(self, d)
|
|
|
|
|
|
|
|
def emulatefrequentlock(*args):
|
|
|
|
raise OSError(errno.EEXIST, "File exists")
|
|
|
|
def emulatefrequentunlock(*args):
|
|
|
|
raise OSError(errno.ENOENT, "No such file or directory")
|
|
|
|
|
|
|
|
state.vfs.makelock = emulatefrequentlock
|
|
|
|
state.vfs.readlock = emulatefrequentunlock
|
|
|
|
|
|
|
|
try:
|
|
|
|
state.makelock(timeout=0)
|
|
|
|
self.fail("unexpected lock acquisition")
|
|
|
|
except error.LockHeld as why:
|
|
|
|
self.assertTrue(why.errno == errno.ETIMEDOUT)
|
|
|
|
self.assertTrue(why.locker == "")
|
|
|
|
state.assertlockexists(False)
|
|
|
|
|
2015-09-18 01:38:00 +03:00
|
|
|
if __name__ == '__main__':
|
|
|
|
silenttestrunner.main(__name__)
|