Merge lp://staging/~vila/bzr/525571-lock-bazaar-conf-files into lp://staging/bzr
Status: | Rejected | ||||
---|---|---|---|---|---|
Rejected by: | Vincent Ladeuil | ||||
Proposed branch: | lp://staging/~vila/bzr/525571-lock-bazaar-conf-files | ||||
Merge into: | lp://staging/bzr | ||||
Diff against target: |
893 lines (+410/-101) (has conflicts) 6 files modified
NEWS (+15/-0) bzrlib/builtins.py (+27/-14) bzrlib/config.py (+130/-29) bzrlib/tests/blackbox/test_break_lock.py (+44/-20) bzrlib/tests/test_commands.py (+1/-1) bzrlib/tests/test_config.py (+193/-37) Text conflict in NEWS Text conflict in bzrlib/config.py |
||||
To merge this branch: | bzr merge lp://staging/~vila/bzr/525571-lock-bazaar-conf-files | ||||
Related bugs: |
|
Reviewer | Review Type | Date Requested | Status |
---|---|---|---|
Robert Collins (community) | Needs Fixing | ||
Review via email: mp+28898@code.staging.launchpad.net |
This proposal supersedes a proposal from 2010-06-29.
Description of the change
The precise circumstances leading to the bug remained unclear, so
here it my best guess: somehow one process was able to read a
conf file *while* another process was writing it and failed
because the file was incomplete leading to parse errors.
ConfigObj does:
h = open(infile, 'rb')
to read a config file.
and
h = open(self.filename, 'wb')
to write a config file.
I.e. the read or write can results in several physical IOs that
could explain partial files being read.
Addressing this is done with:
- using an atomic operation to write the config file so readers
can't get a partial file,
- using a lock to guarantee that a single writer is allowed at any time.
The data model we use for config files is that they are used a
database where the records are the (name, value) pairs.
When a value is modified, the whole config file is written to
disk.
Config objects themselve are rarely cached (if ever) so to access
a value we generally load the whole file.
From there, it makes sense to re-read the whole file before
setting a new value so that other updates are taken into account
(*not* doing so means we will reset the values ignoring the
concurrent updates).
So the sequence to set a new value is:
- take a write lock,
- re-read the config file (to take concurrent updates into
account),
- set the new value,
- write the config file,
- unlock
The proposed implementation means that all methods that ends up
writing a config file should be decorated with
@needs_write_lock. This is generally set_user_option() and its
variants (notably set_alias() and unset_alias() for
GlobalConfig).
Note that since we use an atomic write we neglect to protect
readers against concurrent writers. This shouldn't be a problem
in practice and I've been yelled at for suggesting it in a
paranoid attempt to cover all cases. The case at point being:
- a reader opens a file,
- starts to read it (unlikely to not complete in a single IO),
- a writer somehow manages to replace the opened file (unlikely since we
do a rename),
- the OS presents the new content to the reader.
Since I'm not even sure this scenario is valid, I'll wait for
evidence before considering it.
Anyway, machines can crash while a lock is hold so break-lock has
to be updated to handle the config locks. After discussing the
possible implementations with lifeless, we settled on adding a
--conf option that must be specified to break a config lock. The
actual implementation succeeds when asked to break a lock in a
bzrdir where no lock exist, I did the same. We may want to
revisit the behavior for both cases (failing when no lock are
present) but I consider this out of scope for this bug fix.
I didn't enforce the config directory to be '~/.bazaar', it could
as well be '.bzrmeta' or anything.
Unmerged revisions
- 5335. By Vincent Ladeuil
-
Use --config instead of --conf for break-lock.
- 5334. By Vincent Ladeuil
-
Clarify lock scope.
- 5333. By Vincent Ladeuil
-
Final cleanup.
- 5332. By Vincent Ladeuil
-
Implement the --conf option for break-lock as per lifeless suggestion.
* bzrlib/errors.py:
(NoLockDir): Useless, deleted.* bzrlib/config.py:
(LockableConfig.unlock) : NoLockDir is useless, break_lock()
silenty succeeds if the directory doesn't exist.* bzrlib/
tests/blackbox/ test_break_ lock.py:
Tweak the tests.* bzrlib/builtins.py:
(cmd_break_lock): Fix docstring, add a --conf option to deal with
config files. - 5331. By Vincent Ladeuil
-
Add a test to help LockableConfig daughter classes identify methods that needs to be decorated.
- 5330. By Vincent Ladeuil
-
Further clarify NEWS entry.
- 5329. By Vincent Ladeuil
-
Fix docstring.
- 5328. By Vincent Ladeuil
-
Fix wrong bug number and clarify NEWS entries.
- 5327. By Vincent Ladeuil
-
Revert the lock scope to a sane value.
* bzrlib/
tests/test_ config. py:
(TestLockableConfig.test_ writes_ are_serialized)
(TestLockableConfig.test_ read_while_ writing) : Fix the fallouts. * bzrlib/config.py:
(LockableConfig): Wrong idea, the lock needs to be taken arond the
whole value update call, reducing the lock scope to
_write_config_file exclude the config file re-read.
(GlobalConfig.set_user_ option, GlobalConfig. set_alias)
(GlobalConfig.unset_alias, LocationConfig. set_user_ option) : These
methods needs to be decorated with needs_write_lock to enforce the
design constraints (lock, re-read config, set new value, write
config, unlock). - 5326. By Vincent Ladeuil
-
Add some comments.
-----BEGIN PGP SIGNED MESSAGE-----
Hash: SHA1
Vincent Ladeuil wrote: /bugs.launchpad .net/bugs/ 525571 config. LockableConfig that plugins authors using config files in ~/.bazaar may want to inherit (as LocationConfig and GlobalConfig do now).
> Vincent Ladeuil has proposed merging lp:~vila/bzr/525571-lock-bazaar-conf-files into lp:bzr.
>
> Requested reviews:
> bzr-core (bzr-core)
> Related bugs:
> #525571 No locking when updating files in ~/.bazaar
> https:/
>
>
> This implements a write lock on LocationConfig and GlobalConfig and add support for them in break-lock
> as required for bug #525571.
>
> There is no user nor dev visible change but I'll welcome feedback from plugin authors.
>
> There is a new bzrlib.
>
> I had to do some cleanup in the tests as modifying the model made quite a few of them fail
> (congrats to test authors: failing tests are good tests ! :)
>
> So I made a bit more cleanup than strictly necessary (during failure analysis),
> my apologies to the reviewers.
>
As a comment, without having really read the code thoroughly.
How does this handle stuff like 2 branches locking concurrently
locations.conf. I don't know how often we do it internally, though.
I think lots of filesystem locks on the bazaar directory could adversely
affect performance on Windows. IME locking isn't too expensive if you do
it 1 or 2 times. But if you lock and unlock on every attribute that gets
set, then it probably starts to be an issue.
On a Windows host:
$ TIMEIT -s "b = Branch.open('.')" "b.lock_write(); b.unlock()"
10.5msec
On an Ubuntu VM on the same machine:
$ TIMEIT -s "b = Branch.open('.')" "b.lock_write(); b.unlock()"
1.55msec
John enigmail. mozdev. org/
=:->
-----BEGIN PGP SIGNATURE-----
Version: GnuPG v1.4.9 (Cygwin)
Comment: Using GnuPG with Mozilla - http://
iEYEARECAAYFAkw qMNkACgkQJdeBCY SNAAO7WACfYZOte 5LfqA4Ro4J6U/ 3ZA2Cf tQjcgx047AYI0sJ MiZlfY
ZhkAoLy3d0+
=mMJj
-----END PGP SIGNATURE-----