Rev 5323: Ensure lockable config files are atomically written. in file:///home/vila/src/bzr/bugs/525571-lock-bazaar-conf-files/
Vincent Ladeuil
v.ladeuil+lp at free.fr
Tue Jun 29 08:36:59 BST 2010
At file:///home/vila/src/bzr/bugs/525571-lock-bazaar-conf-files/
------------------------------------------------------------
revno: 5323
revision-id: v.ladeuil+lp at free.fr-20100629073659-z7cmys2po4ztabr3
parent: v.ladeuil+lp at free.fr-20100628155749-mtntt9jcdvtag69v
committer: Vincent Ladeuil <v.ladeuil+lp at free.fr>
branch nick: 525571-lock-bazaar-conf-files
timestamp: Tue 2010-06-29 09:36:59 +0200
message:
Ensure lockable config files are atomically written.
* bzrlib/tests/test_config.py:
(TestLockableConfig.test_read_while_writing): Ensure the readers
gets the most recent version and that's its valid.
* bzrlib/config.py:
(LockableConfig._write_config_file): An atomic version to ensure
readers don't get partial files.
-------------- next part --------------
=== modified file 'bzrlib/config.py'
--- a/bzrlib/config.py 2010-06-28 15:57:49 +0000
+++ b/bzrlib/config.py 2010-06-29 07:36:59 +0000
@@ -62,6 +62,7 @@
up=pull
"""
+from cStringIO import StringIO
import os
import sys
@@ -501,8 +502,8 @@
def __init__(self, get_filename):
super(LockableConfig, self).__init__(get_filename)
- t = transport.get_transport(config_dir())
- self._lock = lockdir.LockDir(t, 'lock')
+ self.transport = transport.get_transport(config_dir())
+ self._lock = lockdir.LockDir(self.transport, 'lock')
def lock_write(self, token=None):
ensure_config_dir_exists(config_dir())
@@ -511,6 +512,17 @@
def unlock(self):
self._lock.unlock()
+ def _write_config_file(self):
+ fname = self._get_filename()
+ conf_dir = os.path.dirname(fname)
+ # the transport API won't take the windows special case into account
+ # here (see ensure_config_dir_exists).
+ ensure_config_dir_exists(conf_dir)
+ f = StringIO()
+ self._get_parser().write(f)
+ self.transport.put_bytes(os.path.basename(fname), f.getvalue())
+ osutils.copy_ownership_from_path(fname)
+
class GlobalConfig(LockableConfig):
"""The configuration that should be used for a specific location."""
@@ -546,8 +558,6 @@
self._write_config_file()
def _set_option(self, option, value, section):
- conf_dir = os.path.dirname(self._get_filename())
- ensure_config_dir_exists(conf_dir)
if self._parser is not None:
self._parser.reload()
self._get_parser().setdefault(section, {})[option] = value
=== modified file 'bzrlib/tests/test_config.py'
--- a/bzrlib/tests/test_config.py 2010-06-28 15:57:49 +0000
+++ b/bzrlib/tests/test_config.py 2010-06-29 07:36:59 +0000
@@ -504,6 +504,43 @@
c2.set_user_option('one', 'c2')
self.assertEquals('c2', c2.get_user_option('one'))
+ def test_read_while_writing(self):
+ c1 = self.create_config()
+ # We spawn a thread that will pause *during* the write
+ ready_to_write = threading.Event()
+ do_writing = threading.Event()
+ writing_done = threading.Event()
+ c1_orig = c1._write_config_file
+ def c1_write_config_file():
+ ready_to_write.set()
+ # The lock is held we wait for the main thread to decide when to
+ # continue
+ do_writing.wait()
+ c1_orig()
+ writing_done.set()
+ c1._write_config_file = c1_write_config_file
+ def c1_set_option():
+ c1.set_user_option('one', 'c1')
+ 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(do_writing.set)
+ t1.start()
+ # Ensure the thread is ready to write
+ ready_to_write.wait()
+ self.assertTrue(c1._lock.is_held)
+ self.assertEquals('c1', c1.get_user_option('one'))
+ # If we read during the write, we get the old value
+ c2 = self.create_config()
+ self.assertEquals('1', c2.get_user_option('one'))
+ # Let the writing occur and ensure it occurred
+ do_writing.set()
+ writing_done.wait()
+ # Now we get the updated value
+ c3 = self.create_config()
+ self.assertEquals('c1', c3.get_user_option('one'))
+
class TestGetUserOptionAs(TestIniConfig):
More information about the bazaar-commits
mailing list