Rev 5362: More doc and ensure that the config is locked when _write_config_file is called. in file:///home/vila/src/bzr/bugs/525571-lock-bazaar-conf-files/

Vincent Ladeuil v.ladeuil+lp at free.fr
Mon Aug 23 15:04:33 BST 2010


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

------------------------------------------------------------
revno: 5362
revision-id: v.ladeuil+lp at free.fr-20100823140433-03cyut95alojc8be
parent: v.ladeuil+lp at free.fr-20100823124549-uoszb3y8ih24kncm
committer: Vincent Ladeuil <v.ladeuil+lp at free.fr>
branch nick: lockable-config-files
timestamp: Mon 2010-08-23 16:04:33 +0200
message:
  More doc and ensure that the config is locked when _write_config_file is called.
-------------- next part --------------
=== modified file 'bzrlib/config.py'
--- a/bzrlib/config.py	2010-08-23 12:45:49 +0000
+++ b/bzrlib/config.py	2010-08-23 14:04:33 +0000
@@ -535,6 +535,24 @@
 
     If several processes try to write the config file, the accesses need to be
     serialized.
+
+    Daughter classes should decorate all methods that update a config with the
+    ``@needs_write_lock`` decorator (they call, directly or indirectly, the
+    ``_write_config_file()`` method. These methods (typically ``set_option()``
+    and variants must reload the config file from disk before calling
+    ``_write_config_file()``), this can be achieved by calling the
+    ``self.reload()`` method. Note that the lock scope should cover both the
+    reading and the writing of the config file which is why the decorator can't
+    be applied to ``_write_config_file()`` only.
+
+    This should be enough to implement the following logic:
+    - lock for exclusive write access,
+    - reload the config file from disk,
+    - set the new value
+    - unlock
+
+    This logic guarantees that a writer can update a value without erasing an
+    update made by another writer.
     """
 
     lock_name = 'lock'
@@ -547,12 +565,23 @@
         self._lock = lockdir.LockDir(self.transport, 'lock')
 
     def lock_write(self, token=None):
+        """Takes a write lock in the directory containing the config file.
+
+        If the directory doesn't exist it is created.
+        """
         ensure_config_dir_exists(self.dir)
         return self._lock.lock_write(token)
 
     def unlock(self):
         self._lock.unlock()
 
+    def _write_config_file(self):
+        if self._lock is None or not self._lock.is_held:
+            # NB: if the following exception is raised it probably means a
+            # missing @needs_write_lock decorator on one of the callers.
+            raise errors.ObjectNotLocked(self)
+        super(LockableConfig, self)._write_config_file()
+
 
 class GlobalConfig(LockableConfig):
     """The configuration that should be used for a specific location."""
@@ -576,10 +605,12 @@
         else:
             return {}
 
+    @needs_write_lock
     def set_alias(self, alias_name, alias_command):
         """Save the alias in the configuration."""
         self._set_option(alias_name, alias_command, 'ALIASES')
 
+    @needs_write_lock
     def unset_alias(self, alias_name):
         """Unset an existing alias."""
         aliases = self._get_parser().get('ALIASES')

=== modified file 'bzrlib/tests/test_config.py'
--- a/bzrlib/tests/test_config.py	2010-08-23 12:45:49 +0000
+++ b/bzrlib/tests/test_config.py	2010-08-23 14:04:33 +0000
@@ -417,8 +417,7 @@
         self.path, self.uid, self.gid = path, uid, gid
 
     def test_ini_config_ownership(self):
-        """Ensure that chown is happening during _write_config_file.
-        """
+        """Ensure that chown is happening during _write_config_file"""
         self.requireFeature(features.chown_feature)
         self.overrideAttr(os, 'chown', self._dummy_chown)
         self.path = self.uid = self.gid = None
@@ -485,7 +484,9 @@
 
     def create_config(self, content):
         c = self.config_class(*self.config_args, _content=content)
+        c.lock_write()
         c._write_config_file()
+        c.unlock()
         return c
 
     def test_simple_read_access(self):
@@ -1166,10 +1167,14 @@
             global_config = sample_config_text
 
         my_global_config = config.GlobalConfig(_content=global_config)
+        my_global_config.lock_write()
         my_global_config._write_config_file()
+        my_global_config.unlock()
         my_location_config = config.LocationConfig(
             my_branch.base, _content=sample_branches_text)
+        my_location_config.lock_write()
         my_location_config._write_config_file()
+        my_location_config.unlock()
 
         my_config = config.BranchConfig(my_branch)
         self.my_config = my_config
@@ -1242,11 +1247,15 @@
         my_branch = FakeBranch(location)
         if global_config is not None:
             my_global_config = config.GlobalConfig(_content=global_config)
+            my_global_config.lock_write()
             my_global_config._write_config_file()
+            my_global_config.unlock()
         if location_config is not None:
             my_location_config = config.LocationConfig(my_branch.base,
                                                        _content=location_config)
+            my_location_config.lock_write()
             my_location_config._write_config_file()
+            my_location_config.unlock()
         my_config = config.BranchConfig(my_branch)
         if branch_data_config is not None:
             my_config.branch.control_files.files['branch.conf'] = \



More information about the bazaar-commits mailing list