Rev 5358: Start implementing config files locking. in file:///home/vila/src/bzr/bugs/525571-lock-bazaar-conf-files/
Vincent Ladeuil
v.ladeuil+lp at free.fr
Thu Jul 22 09:35:45 BST 2010
At file:///home/vila/src/bzr/bugs/525571-lock-bazaar-conf-files/
------------------------------------------------------------
revno: 5358
revision-id: v.ladeuil+lp at free.fr-20100722083545-1qvav61t0d4rrojc
parent: v.ladeuil+lp at free.fr-20100722081215-qvjirmudwj527spb
committer: Vincent Ladeuil <v.ladeuil+lp at free.fr>
branch nick: lockable-config-files
timestamp: Thu 2010-07-22 10:35:45 +0200
message:
Start implementing config files locking.
* bzrlib/tests/test_config.py:
(TestLockableConfig): Add a test for concurrent writers.
* bzrlib/config.py:
(LockableConfig): Some configs need to be locked (GlobalConfig and
LocationConfig at least).
(GlobalConfig.set_user_option): Needs a write lock to protect
against concurrent writes.
-------------- next part --------------
=== modified file 'bzrlib/config.py'
--- a/bzrlib/config.py 2010-07-22 07:56:13 +0000
+++ b/bzrlib/config.py 2010-07-22 08:35:45 +0000
@@ -65,6 +65,7 @@
import os
import sys
+from bzrlib.decorators import needs_write_lock
from bzrlib.lazy_import import lazy_import
lazy_import(globals(), """
import errno
@@ -77,11 +78,13 @@
atomicfile,
debug,
errors,
+ lockdir,
mail_client,
osutils,
registry,
symbol_versioning,
trace,
+ transport,
ui,
urlutils,
win32utils,
@@ -526,7 +529,28 @@
atomic_file.close()
-class GlobalConfig(IniBasedConfig):
+class LockableConfig(IniBasedConfig):
+ """A configuration needing explicit locking for access.
+
+ If several processes try to write the config file, the accesses need to be
+ serialized.
+ """
+
+ def __init__(self, file_name, _content=None):
+ super(LockableConfig, self).__init__(file_name=file_name,
+ _content=_content)
+ t = transport.get_transport(config_dir())
+ self._lock = lockdir.LockDir(t, 'lock')
+
+ def lock_write(self, token=None):
+ ensure_config_dir_exists(config_dir())
+ return self._lock.lock_write(token)
+
+ def unlock(self):
+ self._lock.unlock()
+
+
+class GlobalConfig(LockableConfig):
"""The configuration that should be used for a specific location."""
def __init__(self, _content=None):
@@ -536,6 +560,7 @@
def get_editor(self):
return self._get_user_option('editor')
+ @needs_write_lock
def set_user_option(self, option, value):
"""Save option and its value in the configuration."""
self._set_option(option, value, 'DEFAULT')
@@ -560,7 +585,6 @@
self._write_config_file()
def _set_option(self, option, value, section):
- # FIXME: RBC 20051029 This should take a file lock on bazaar.conf.
self.reload()
self._get_parser().setdefault(section, {})[option] = value
self._write_config_file()
=== modified file 'bzrlib/tests/test_config.py'
--- a/bzrlib/tests/test_config.py 2010-07-22 08:12:15 +0000
+++ b/bzrlib/tests/test_config.py 2010-07-22 08:35:45 +0000
@@ -19,6 +19,7 @@
from cStringIO import StringIO
import os
import sys
+import threading
#import bzrlib specific imports here
from bzrlib import (
@@ -472,6 +473,42 @@
c1.set_user_option('two', 'done')
self.assertEquals('c2', c1._get_user_option('one'))
+ def test_writes_are_serialized(self):
+ c1 = self.create_config(self._content)
+ c2 = self.create_config(self._content)
+
+ # We spawn a thread that will pause *during* the write
+ before_writing = threading.Event()
+ after_writing = threading.Event()
+ writing_done = threading.Event()
+ c1_orig = c1._write_config_file
+ def c1_write_config_file():
+ before_writing.set()
+ c1_orig()
+ # The lock is held we wait for the main thread to decide when to
+ # continue
+ after_writing.wait()
+ c1._write_config_file = c1_write_config_file
+ def c1_set_option():
+ c1.set_user_option('one', 'c1')
+ writing_done.set()
+ t1 = threading.Thread(target=c1_set_option)
+ # Collect the thread after the test
+ self.addCleanup(t1.join)
+ # Be ready to unblock the thread if the test goes wrong
+ self.addCleanup(after_writing.set)
+ t1.start()
+ before_writing.wait()
+ self.assertTrue(c1._lock.is_held)
+ self.assertRaises(errors.LockContention,
+ c2.set_user_option, 'one', 'c2')
+ self.assertEquals('c1', c1.get_user_option('one'))
+ # Let the lock be released
+ after_writing.set()
+ writing_done.wait()
+ c2.set_user_option('one', 'c2')
+ self.assertEquals('c2', c2.get_user_option('one'))
+
class TestGetUserOptionAs(TestIniConfig):
More information about the bazaar-commits
mailing list