[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