[PATCH 4 of 4 stable] PROOF OF CONCEPT: introduce lock validation
Mads Kiilerich
mads at kiilerich.com
Sat May 12 13:37:46 CDT 2012
# HG changeset patch
# User Mads Kiilerich <mads at kiilerich.com>
# Date 1326326392 -3600
# Node ID 715dc5d131f5d690b32925a50a80ded2bb4b8609
# Parent 4a112c51f224358f5c269b06d6ee82f43b6c9133
PROOF OF CONCEPT: introduce lock validation
Instrumenting critical places with these checks can help verifying compliance
with the locking strategy described on
http://mercurial.selenic.com/wiki/LockingDesign.
Occasionally running the test suite with this patch might catch some locking
errors, but the checking as it is is probably too intrusive for inclusion in
core Mercurial.
diff --git a/hgext/keyword.py b/hgext/keyword.py
--- a/hgext/keyword.py
+++ b/hgext/keyword.py
@@ -436,7 +436,11 @@
repo[None].add([fn])
ui.note(_('\nkeywords written to %s:\n') % fn)
ui.note(keywords)
- repo.dirstate.setbranch('demobranch')
+ wlock = repo.wlock()
+ try:
+ repo.dirstate.setbranch('demobranch')
+ finally:
+ wlock.release()
for name, cmd in ui.configitems('hooks'):
if name.split('.', 1)[0].find('commit') > -1:
repo.ui.setconfig('hooks', name, '')
diff --git a/hgext/mq.py b/hgext/mq.py
--- a/hgext/mq.py
+++ b/hgext/mq.py
@@ -2443,6 +2443,7 @@
setupheaderopts(ui, opts)
wlock = repo.wlock()
try:
+ repo.checkwlock()
ret = q.refresh(repo, pats, msg=message, **opts)
q.savedirty()
return ret
@@ -2536,6 +2537,7 @@
diffopts = q.patchopts(q.diffopts(), *patches)
wlock = repo.wlock()
try:
+ repo.checkwlock()
q.refresh(repo, msg=message, git=diffopts.git)
q.delete(repo, patches, opts)
q.savedirty()
@@ -2957,6 +2959,7 @@
if update and opts.get('keep'):
wlock = repo.wlock()
try:
+ repo.checkwlock()
urev = repo.mq.qparents(repo, revs[0])
repo.dirstate.rebuild(urev, repo[urev].manifest())
repo.dirstate.write()
diff --git a/mercurial/dirstate.py b/mercurial/dirstate.py
--- a/mercurial/dirstate.py
+++ b/mercurial/dirstate.py
@@ -259,6 +259,7 @@
return copies
def setbranch(self, branch):
+ assert os.path.lexists(self._join('.hg/wlock'))
if branch in ['tip', '.', 'null']:
raise util.Abort(_('the name \'%s\' is reserved') % branch)
self._branch = encoding.fromlocal(branch)
@@ -494,6 +495,7 @@
self._dirty = True
def write(self):
+ assert os.path.lexists(self._join('.hg/wlock'))
if not self._dirty:
return
st = self._opener("dirstate", "w", atomictemp=True)
diff --git a/mercurial/localrepo.py b/mercurial/localrepo.py
--- a/mercurial/localrepo.py
+++ b/mercurial/localrepo.py
@@ -102,10 +102,6 @@
self.sopener = self.store.opener
self.sjoin = self.store.join
self.opener.createmode = self.store.createmode
- self._applyrequirements(requirements)
- if create:
- self._writerequirements()
-
self._branchcache = None
self._branchcachetip = None
@@ -119,6 +115,10 @@
# Maps a property name to its util.filecacheentry
self._filecache = {}
+ self._applyrequirements(requirements)
+ if create:
+ self._writerequirements()
+
def _applyrequirements(self, requirements):
self.requirements = requirements
openerreqs = set(('revlogv1', 'generaldelta'))
@@ -126,6 +126,7 @@
if r in openerreqs)
def _writerequirements(self):
+ #self.checklock()
reqfile = self.opener("requires", "w")
for r in self.requirements:
reqfile.write("%s\n" % r)
@@ -178,7 +179,7 @@
return bookmarks.readcurrent(self)
def _writebookmarks(self, marks):
- bookmarks.write(self)
+ bookmarks.write(self)
@storecache('phaseroots')
def _phasecache(self):
@@ -257,6 +258,7 @@
tag_disallowed = ':\r\n'
def _tag(self, names, node, message, local, user, date, extra={}):
+ self.checklock()
if isinstance(names, str):
allchars = names
names = (names,)
@@ -540,6 +542,7 @@
return partial, last, lrev
def _writebranchcache(self, branches, tip, tiprev):
+ # self.checklock()
try:
f = self.opener("cache/branchheads", "w", atomictemp=True)
f.write("%s %s\n" % (hex(tip), tiprev))
@@ -710,6 +713,7 @@
return self._filter(self._decodefilterpats, filename, data)
def transaction(self, desc):
+ self.checklock()
tr = self._transref and self._transref() or None
if tr and tr.running():
return tr.nest()
@@ -739,6 +743,7 @@
return [undoname(x) for x in self._journalfiles()]
def _writejournal(self, desc):
+ self.checklock()
self.opener.write("journal.dirstate",
self.opener.tryread("dirstate"))
self.opener.write("journal.branch",
@@ -888,8 +893,8 @@
except error.LockHeld, inst:
if not wait:
raise
- self.ui.warn(_("waiting for lock on %s held by %r\n") %
- (desc, inst.locker))
+ self.ui.warnstack(_("%s waiting for %s on %s held by %r\n") %
+ (os.getpid(), lockname, desc, inst.locker), skip=2)
# default to 600 seconds timeout
l = lock.lock(lockname, int(self.ui.config("ui", "timeout", "600")),
releasefn, desc=desc)
@@ -930,6 +935,11 @@
self._lockref = weakref.ref(l)
return l
+ def checklock(self):
+ l = self._lockref and self._lockref()
+ if l is None or not l.held:
+ self.ui.warnstack('no lock', skip=2)
+
def wlock(self, wait=True):
'''Lock the non-store parts of the repository (everything under
.hg except .hg/store) and return a weak reference to the lock.
@@ -939,6 +949,10 @@
l.lock()
return l
+ s = self._lockref and self._lockref()
+ if s is not None and s.held:
+ self.ui.warnstack('lock taken when trying to take wlock', skip=2)
+
def unlock():
self.dirstate.write()
ce = self._filecache.get('dirstate')
@@ -951,6 +965,11 @@
self._wlockref = weakref.ref(l)
return l
+ def checkwlock(self):
+ l = self._wlockref and self._wlockref()
+ if l is None or not l.held:
+ self.ui.warnstack('no wlock', skip=2)
+
def _filecommit(self, fctx, manifest1, manifest2, linkrev, tr, changelist):
"""
commit an individual file as part of a larger transaction
diff --git a/mercurial/ui.py b/mercurial/ui.py
--- a/mercurial/ui.py
+++ b/mercurial/ui.py
@@ -685,6 +685,15 @@
traceback.print_exc(file=self.ferr)
return self.tracebackflag
+ def warnstack(self, msg, skip=1):
+ '''issue warning with the message and the current stack, skipping the
+ skip last entries'''
+ self.warn('%s at:\n' % msg)
+ entries = traceback.extract_stack()[:-skip]
+ fnmax = max(len(entry[0]) for entry in entries)
+ for fn, ln, func, _text in entries:
+ self.warn(' %*s:%-4s in %s\n' % (fnmax, fn, ln, func))
+
def geteditor(self):
'''return editor to use'''
if sys.platform == 'plan9':
diff --git a/tests/run-tests.py b/tests/run-tests.py
--- a/tests/run-tests.py
+++ b/tests/run-tests.py
@@ -834,6 +834,7 @@
hgrc = open(HGRCPATH, 'w+')
hgrc.write('[ui]\n')
hgrc.write('slash = True\n')
+ hgrc.write('timeout = 5\n')
hgrc.write('[defaults]\n')
hgrc.write('backout = -d "0 0"\n')
hgrc.write('commit = -d "0 0"\n')
diff --git a/tests/test-commandserver.py.out b/tests/test-commandserver.py.out
--- a/tests/test-commandserver.py.out
+++ b/tests/test-commandserver.py.out
@@ -76,6 +76,7 @@
defaults.commit=-d "0 0"
defaults.tag=-d "0 0"
ui.slash=True
+ui.timeout=5
ui.foo=bar
runcommand init foo
runcommand -R foo showconfig ui defaults
@@ -83,6 +84,7 @@
defaults.commit=-d "0 0"
defaults.tag=-d "0 0"
ui.slash=True
+ui.timeout=5
testing hookoutput:
More information about the Mercurial-devel
mailing list