[PATCH 19 of 19] localrepo: introduce lock validation

Mads Kiilerich mads at kiilerich.com
Thu Jan 12 19:32:53 CST 2012


# HG changeset patch
# User Mads Kiilerich <mads at kiilerich.com>
# Date 1326326392 -3600
# Node ID a624c5a3c226f20ab0585a24e37061bba28b7183
# Parent  87bc847a7ede54c4c1637e07d3ac31da08adb944
localrepo: 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/mq.py b/hgext/mq.py
--- a/hgext/mq.py
+++ b/hgext/mq.py
@@ -2283,6 +2283,7 @@
         # We don't want to lose the patch message if qrefresh fails (issue2062)
         repo.savecommitmessage(message)
     setupheaderopts(ui, opts)
+    repo.checkwlock()
     return q.refresh(repo, pats, msg=message, **opts)
 
 @command("^qdiff",
@@ -2371,6 +2372,7 @@
         message = ui.edit(message, user or ui.username())
 
     diffopts = q.patchopts(q.diffopts(), *patches)
+    repo.checkwlock()
     q.refresh(repo, msg=message, git=diffopts.git)
     q.delete(repo, patches, opts)
 
@@ -2761,6 +2763,7 @@
 
     revs = list(rootnodes)
     if update and opts.get('keep'):
+        repo.checkwlock()
         urev = repo.mq.qparents(repo, revs[0])
         repo.dirstate.rebuild(urev, repo[urev].manifest())
         repo.dirstate.write()
diff --git a/mercurial/localrepo.py b/mercurial/localrepo.py
--- a/mercurial/localrepo.py
+++ b/mercurial/localrepo.py
@@ -170,7 +170,7 @@
         return bookmarks.readcurrent(self)
 
     def _writebookmarks(self, marks):
-      bookmarks.write(self)
+        bookmarks.write(self)
 
     @filecache('phaseroots')
     def _phaseroots(self):
@@ -264,6 +264,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,)
@@ -725,6 +726,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()
@@ -745,6 +747,7 @@
         return tr
 
     def _writejournal(self, desc):
+        self.checklock()
         # save dirstate for rollback
         try:
             ds = self.opener.read("dirstate")
@@ -904,8 +907,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)
@@ -944,6 +947,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.
@@ -953,6 +961,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')
@@ -965,6 +977,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
@@ -673,6 +673,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'''
         return (os.environ.get("HGEDITOR") or
diff --git a/tests/run-tests.py b/tests/run-tests.py
--- a/tests/run-tests.py
+++ b/tests/run-tests.py
@@ -826,6 +826,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