Rev 5325: Implement break-lock for config files. in file:///home/vila/src/bzr/bugs/525571-lock-bazaar-conf-files/

Vincent Ladeuil v.ladeuil+lp at free.fr
Tue Jun 29 15:55:53 BST 2010


At file:///home/vila/src/bzr/bugs/525571-lock-bazaar-conf-files/

------------------------------------------------------------
revno: 5325
revision-id: v.ladeuil+lp at free.fr-20100629145553-gay36b3uuodjbya7
parent: v.ladeuil+lp at free.fr-20100629091911-uuzfksmfm0z9oxtq
committer: Vincent Ladeuil <v.ladeuil+lp at free.fr>
branch nick: 525571-lock-bazaar-conf-files
timestamp: Tue 2010-06-29 16:55:53 +0200
message:
  Implement break-lock for config files.
  
  * bzrlib/tests/blackbox/test_break_lock.py:
  (TestConfigBreakLock): Test break-lock and broken lock behaviors
  for config files.
  
  * bzrlib/errors.py:
  (NoLockDir): New error.
  
  * bzrlib/config.py:
  (LockableConfig.break_lock): Break the lock but raise NoLock if
  the 'lock' directory doesn't exit.
  
  * bzrlib/builtins.py:
  (cmd_break_lock.run): Can now break config locks.
-------------- next part --------------
=== modified file 'NEWS'
--- a/NEWS	2010-06-23 08:14:21 +0000
+++ b/NEWS	2010-06-29 14:55:53 +0000
@@ -36,6 +36,13 @@
 New Features
 ************
 
+* ``bzr break-lock`` will now break configuration locks if the location
+  provided contains a broken lock. (Vincent Ladeuil, #591215)
+
+* ``bzrlib.config.LockableConfig`` is a base class for config files that
+  needs to be protected against multiple writers. 
+  (Vincent Ladeuil, #591215)
+
 * Support ``--directory`` option for a number of additional commands:
   conflicts, merge-directive, missing, resolve, shelve, switch,
   unshelve, whoami. (Martin von Gagern, #527878)
@@ -54,6 +61,9 @@
   or pull location in locations.conf or branch.conf.
   (Gordon Tyler, #534787)
 
+* Configuration files in ``${BZR_HOME}`` are now protected
+  against concurrent writers. (Vincent Ladeuil, #525571)
+
 * Ensure that wrong path specifications in ``BZR_PLUGINS_AT`` display
   proper error messages. (Vincent Ladeuil, #591215)
 

=== modified file 'bzrlib/builtins.py'
--- a/bzrlib/builtins.py	2010-06-17 08:53:15 +0000
+++ b/bzrlib/builtins.py	2010-06-29 14:55:53 +0000
@@ -4830,7 +4830,8 @@
 
 
 class cmd_break_lock(Command):
-    __doc__ = """Break a dead lock on a repository, branch or working directory.
+    __doc__ = """\
+    Break a dead lock on a repository, branch, working directory or config file.
 
     CAUTION: Locks should only be broken when you are sure that the process
     holding the lock has been stopped.
@@ -4841,17 +4842,24 @@
     :Examples:
         bzr break-lock
         bzr break-lock bzr+ssh://example.com/bzr/foo
+        bzr break-lock ~/.bazaar
     """
     takes_args = ['location?']
 
     def run(self, location=None, show=False):
         if location is None:
             location = u'.'
-        control, relpath = bzrdir.BzrDir.open_containing(location)
         try:
-            control.break_lock()
-        except NotImplementedError:
-            pass
+            # Config locks first
+            conf = config.LockableConfig(lambda : location)
+            conf.break_lock()
+        except errors.NoLockDir:
+            # Then wt, branch, repo, etc.
+            control, relpath = bzrdir.BzrDir.open_containing(location)
+            try:
+                control.break_lock()
+            except NotImplementedError:
+                pass
 
 
 class cmd_wait_until_signalled(Command):

=== modified file 'bzrlib/config.py'
--- a/bzrlib/config.py	2010-06-29 09:19:11 +0000
+++ b/bzrlib/config.py	2010-06-29 14:55:53 +0000
@@ -509,18 +509,31 @@
     If several processes try to write the config file, the accesses need to be
     serialized.
     """
+    lock_name = 'lock'
+
+    def __init__(self, get_filename):
+        super(LockableConfig, self).__init__(get_filename)
+        self.dir = osutils.dirname(osutils.safe_unicode(self._get_filename()))
+        self.transport = transport.get_transport(self.dir)
+        self.lock = None
 
     def lock_write(self, token=None):
-        conf_dir = os.path.dirname(self._get_filename())
-        ensure_config_dir_exists(conf_dir)
-        self.transport = transport.get_transport(conf_dir)
-        self._lock = lockdir.LockDir(self.transport, 'lock')
+        if self.lock is None:
+            ensure_config_dir_exists(self.dir)
+            self._lock = lockdir.LockDir(self.transport, self.lock_name)
         self._lock.lock_write(token)
         return lock.LogicalLockResult(self.unlock)
 
     def unlock(self):
         self._lock.unlock()
 
+    def break_lock(self):
+        if not self.transport.has(self.lock_name):
+            raise errors.NoLockDir(self.dir)
+        if self.lock is None:
+            self.lock = lockdir.LockDir(self.transport, self.lock_name)
+        self.lock.break_lock()
+
     @needs_write_lock
     def _write_config_file(self):
         self._write_config_file_atomic()

=== modified file 'bzrlib/errors.py'
--- a/bzrlib/errors.py	2010-06-17 11:52:13 +0000
+++ b/bzrlib/errors.py	2010-06-29 14:55:53 +0000
@@ -1083,6 +1083,16 @@
         self.lock = lock
 
 
+class NoLockDir(LockError):
+
+    _fmt = "No lock directory (hence no lock) in: %(path)s"
+
+    internal_error = False
+
+    def __init__(self, path):
+        self.path = path
+
+
 class TokenLockingNotSupported(LockError):
 
     _fmt = "The object %(obj)s does not support token specifying a token when locking."

=== modified file 'bzrlib/tests/blackbox/test_break_lock.py'
--- a/bzrlib/tests/blackbox/test_break_lock.py	2010-06-11 07:32:12 +0000
+++ b/bzrlib/tests/blackbox/test_break_lock.py	2010-06-29 14:55:53 +0000
@@ -18,17 +18,17 @@
 
 import os
 
-import bzrlib
 from bzrlib import (
+    branch,
+    bzrdir,
+    config,
     errors,
     lockdir,
+    tests,
     )
-from bzrlib.branch import Branch
-from bzrlib.bzrdir import BzrDir
-from bzrlib.tests import TestCaseWithTransport
-
-
-class TestBreakLock(TestCaseWithTransport):
+
+
+class TestBreakLock(tests.TestCaseWithTransport):
 
     # General principal for break-lock: All the elements that might be locked
     # by a bzr operation on PATH, are candidates that break-lock may unlock.
@@ -52,14 +52,14 @@
              'repo/',
              'repo/branch/',
              'checkout/'])
-        bzrlib.bzrdir.BzrDir.create('master-repo').create_repository()
-        self.master_branch = bzrlib.bzrdir.BzrDir.create_branch_convenience(
+        bzrdir.BzrDir.create('master-repo').create_repository()
+        self.master_branch = bzrdir.BzrDir.create_branch_convenience(
             'master-repo/master-branch')
-        bzrlib.bzrdir.BzrDir.create('repo').create_repository()
-        local_branch = bzrlib.bzrdir.BzrDir.create_branch_convenience('repo/branch')
+        bzrdir.BzrDir.create('repo').create_repository()
+        local_branch = bzrdir.BzrDir.create_branch_convenience('repo/branch')
         local_branch.bind(self.master_branch)
-        checkoutdir = bzrlib.bzrdir.BzrDir.create('checkout')
-        bzrlib.branch.BranchReferenceFormat().initialize(
+        checkoutdir = bzrdir.BzrDir.create('checkout')
+        branch.BranchReferenceFormat().initialize(
             checkoutdir, target_branch=local_branch)
         self.wt = checkoutdir.create_workingtree()
 
@@ -73,7 +73,7 @@
         # however, we dont test breaking the working tree because we
         # cannot accurately do so right now: the dirstate lock is held
         # by an os lock, and we need to spawn a separate process to lock it
-        # thne kill -9 it.
+        # then kill -9 it.
         # sketch of test:
         # lock most of the dir:
         self.wt.branch.lock_write()
@@ -82,22 +82,34 @@
         # we need 5 yes's - wt, branch, repo, bound branch, bound repo.
         self.run_bzr('break-lock checkout', stdin="y\ny\ny\ny\n")
         # a new tree instance should be lockable
-        branch = bzrlib.branch.Branch.open('checkout')
-        branch.lock_write()
-        branch.unlock()
+        b = branch.Branch.open('checkout')
+        b.lock_write()
+        b.unlock()
         # and a new instance of the master branch
-        mb = branch.get_master_branch()
+        mb = b.get_master_branch()
         mb.lock_write()
         mb.unlock()
         self.assertRaises(errors.LockBroken, self.wt.unlock)
         self.assertRaises(errors.LockBroken, self.master_branch.unlock)
 
 
-class TestBreakLockOldBranch(TestCaseWithTransport):
+class TestBreakLockOldBranch(tests.TestCaseWithTransport):
 
     def test_break_lock_format_5_bzrdir(self):
         # break lock on a format 5 bzrdir should just return
-        self.make_branch_and_tree('foo', format=bzrlib.bzrdir.BzrDirFormat5())
+        self.make_branch_and_tree('foo', format=bzrdir.BzrDirFormat5())
         out, err = self.run_bzr('break-lock foo')
         self.assertEqual('', out)
         self.assertEqual('', err)
+
+
+class TestConfigBreakLock(tests.TestCaseWithTransport):
+
+    def test_break_lock(self):
+        def config_file_name():
+            return './my.conf'
+        self.build_tree_contents([(config_file_name(), '[DEFAULT]\none=1\n')])
+        c1 = config.LockableConfig(config_file_name)
+        c1.lock_write()
+        self.run_bzr('break-lock %s' % config_file_name(), stdin="y\n")
+        self.assertRaises(errors.LockBroken, c1.unlock)

=== modified file 'bzrlib/tests/test_config.py'
--- a/bzrlib/tests/test_config.py	2010-06-29 09:19:11 +0000
+++ b/bzrlib/tests/test_config.py	2010-06-29 14:55:53 +0000
@@ -497,7 +497,7 @@
         # Collect the thread after the test
         self.addCleanup(t1.join)
         # Be ready to unblock the thread if the test goes wrong
-        self.addCleanup(writing_done.set)
+        self.addCleanup(after_writing.set)
         t1.start()
         before_writing.wait()
         self.assertTrue(c1._lock.is_held)



More information about the bazaar-commits mailing list