comparison mercurial/localrepo.py @ 33436:9bb4decd43b0

repovfs: add a ward to check if locks are properly taken When the appropriate developer warnings are enabled, We wrap 'repo.vfs.audit' to check for locks when accessing file in '.hg' for writing. Another changeset will add a 'ward' for the store vfs (svfs). This check system has caught a handful of locking issues that have been fixed in previous series (mostly in 4.0). I expect another batch to be caught in third party extensions. We introduce two real exceptions from extensions 'blackbox.log' (because a lot of read-only operations add entry to it), and 'last-email.txt' (because 'hg email' is currently a read only operation and there is value to keep it this way). In addition we are currently allowing bisect to operate outside of the lock because the current code is a bit hard to get properly locked for now. Multiple clean up have been made but there is still a couple of them to do and the freeze is coming.
author Boris Feld <boris.feld@octobus.net>
date Tue, 11 Jul 2017 12:38:17 +0200
parents 1bb209d08a34
children 0720e6265c8a
comparison
equal deleted inserted replaced
33435:456626e9c3d1 33436:9bb4decd43b0
298 298
299 # a list of (ui, featureset) functions. 299 # a list of (ui, featureset) functions.
300 # only functions defined in module of enabled extensions are invoked 300 # only functions defined in module of enabled extensions are invoked
301 featuresetupfuncs = set() 301 featuresetupfuncs = set()
302 302
303 # list of prefix for file which can be written without 'wlock'
304 # Extensions should extend this list when needed
305 _wlockfreeprefix = {
306 # We migh consider requiring 'wlock' for the next
307 # two, but pretty much all the existing code assume
308 # wlock is not needed so we keep them excluded for
309 # now.
310 'hgrc',
311 'requires',
312 # XXX cache is a complicatged business someone
313 # should investigate this in depth at some point
314 'cache/',
315 # XXX shouldn't be dirstate covered by the wlock?
316 'dirstate',
317 # XXX bisect was still a bit too messy at the time
318 # this changeset was introduced. Someone should fix
319 # the remainig bit and drop this line
320 'bisect.state',
321 }
322
303 def __init__(self, baseui, path, create=False): 323 def __init__(self, baseui, path, create=False):
304 self.requirements = set() 324 self.requirements = set()
305 self.filtername = None 325 self.filtername = None
306 # wvfs: rooted at the repository root, used to access the working copy 326 # wvfs: rooted at the repository root, used to access the working copy
307 self.wvfs = vfsmod.vfs(path, expandpath=True, realpath=True) 327 self.wvfs = vfsmod.vfs(path, expandpath=True, realpath=True)
317 # These auditor are not used by the vfs, 337 # These auditor are not used by the vfs,
318 # only used when writing this comment: basectx.match 338 # only used when writing this comment: basectx.match
319 self.auditor = pathutil.pathauditor(self.root, self._checknested) 339 self.auditor = pathutil.pathauditor(self.root, self._checknested)
320 self.nofsauditor = pathutil.pathauditor(self.root, self._checknested, 340 self.nofsauditor = pathutil.pathauditor(self.root, self._checknested,
321 realfs=False) 341 realfs=False)
322 self.vfs = vfsmod.vfs(self.path)
323 self.baseui = baseui 342 self.baseui = baseui
324 self.ui = baseui.copy() 343 self.ui = baseui.copy()
325 self.ui.copy = baseui.copy # prevent copying repo configuration 344 self.ui.copy = baseui.copy # prevent copying repo configuration
345 self.vfs = vfsmod.vfs(self.path)
346 if (self.ui.configbool('devel', 'all-warnings') or
347 self.ui.configbool('devel', 'check-locks')):
348 self.vfs.audit = self._getvfsward(self.vfs.audit)
326 # A list of callback to shape the phase if no data were found. 349 # A list of callback to shape the phase if no data were found.
327 # Callback are in the form: func(repo, roots) --> processed root. 350 # Callback are in the form: func(repo, roots) --> processed root.
328 # This list it to be filled by extension during repo setup 351 # This list it to be filled by extension during repo setup
329 self._phasedefaults = [] 352 self._phasedefaults = []
330 try: 353 try:
438 461
439 # Key to signature value. 462 # Key to signature value.
440 self._sparsesignaturecache = {} 463 self._sparsesignaturecache = {}
441 # Signature to cached matcher instance. 464 # Signature to cached matcher instance.
442 self._sparsematchercache = {} 465 self._sparsematchercache = {}
466
467 def _getvfsward(self, origfunc):
468 """build a ward for self.vfs"""
469 rref = weakref.ref(self)
470 def checkvfs(path, mode=None):
471 ret = origfunc(path, mode=mode)
472 repo = rref()
473 if (repo is None
474 or not util.safehasattr(repo, '_wlockref')
475 or not util.safehasattr(repo, '_lockref')):
476 return
477 if mode in (None, 'r', 'rb'):
478 return
479 if path.startswith(repo.path):
480 # truncate name relative to the repository (.hg)
481 path = path[len(repo.path) + 1:]
482 if path.startswith('journal.'):
483 # journal is covered by 'lock'
484 if repo._currentlock(repo._lockref) is None:
485 repo.ui.develwarn('write with no lock: "%s"' % path,
486 stacklevel=2)
487 elif repo._currentlock(repo._wlockref) is None:
488 # rest of vfs files are covered by 'wlock'
489 #
490 # exclude special files
491 for prefix in self._wlockfreeprefix:
492 if path.startswith(prefix):
493 return
494 repo.ui.develwarn('write with no wlock: "%s"' % path,
495 stacklevel=2)
496 return ret
497 return checkvfs
443 498
444 def close(self): 499 def close(self):
445 self._writecaches() 500 self._writecaches()
446 501
447 def _loadextensions(self): 502 def _loadextensions(self):