Mercurial > public > mercurial-scm > hg
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): |