Merge lp:~spiv/bzr/log-objectnotlocked-bug-445171 into lp:bzr/2.0

Proposed by Andrew Bennetts
Status: Merged
Approved by: John A Meinel
Approved revision: no longer in the source branch.
Merged at revision: not available
Proposed branch: lp:~spiv/bzr/log-objectnotlocked-bug-445171
Merge into: lp:bzr/2.0
Diff against target: 208 lines
5 files modified
NEWS (+6/-0)
bzrlib/builtins.py (+54/-47)
bzrlib/log.py (+2/-0)
bzrlib/merge_directive.py (+4/-2)
bzrlib/tests/__init__.py (+4/-0)
To merge this branch: bzr merge lp:~spiv/bzr/log-objectnotlocked-bug-445171
Reviewer Review Type Date Requested Status
John A Meinel Approve
Review via email: mp+14135@code.launchpad.net
To post a comment you must log in.
Revision history for this message
Andrew Bennetts (spiv) wrote :

This fixes ObjectNotLocked errors during actions like "bzr log -r 199 NEWS" in a branch of lp:bzr. The basic change is pretty simple: acquire the read_lock as soon as cmd_log acquires the branch, and hold it until the end of cmd_log. This case was supposedly already tested by blackbox tests, but because of the global chk_map page cache the bug wasn't being exposed. So this patch also adjusts TestCase._run_bzr_core to clear that global cache. We should perhaps look at what it would take to make that cache truly semantically transparent, e.g. always require a read lock even if the page is in the cache.

This also has the benefit that "bzr -Drelock log ..." is clean now :)

Revision history for this message
John A Meinel (jameinel) wrote :

-----BEGIN PGP SIGNED MESSAGE-----
Hash: SHA1

Andrew Bennetts wrote:
> Andrew Bennetts has proposed merging lp:~spiv/bzr/log-objectnotlocked-bug-445171 into lp:bzr/2.0.
>
> Requested reviews:
> bzr-core (bzr-core)
> Related bugs:
> #445171 ObjectNotLocked doing 'bzr log -r 199 path/to/file'
> https://bugs.launchpad.net/bugs/445171
>
>
> This fixes ObjectNotLocked errors during actions like "bzr log -r 199 NEWS" in a branch of lp:bzr. The basic change is pretty simple: acquire the read_lock as soon as cmd_log acquires the branch, and hold it until the end of cmd_log. This case was supposedly already tested by blackbox tests, but because of the global chk_map page cache the bug wasn't being exposed. So this patch also adjusts TestCase._run_bzr_core to clear that global cache. We should perhaps look at what it would take to make that cache truly semantically transparent, e.g. always require a read lock even if the page is in the cache.
>
> This also has the benefit that "bzr -Drelock log ..." is clean now :)
>

This is starting to look like something that could benefit from
OperationWithCleanups :)

So what this *breaks* is 'bzr log' with '-r branch:' (I believe),
because -r branch:path acquires a write lock to fetch the remote
revisions locally. Note that -r -1:path *doesn't* and refers to the same
revision-id....

That said, I think it is the right thing to do.

 review: approve
 merge: approve

John
=:->

-----BEGIN PGP SIGNATURE-----
Version: GnuPG v1.4.9 (Cygwin)
Comment: Using GnuPG with Mozilla - http://enigmail.mozdev.org/

iEYEARECAAYFAkrpHE4ACgkQJdeBCYSNAAPS9ACfbE989hOUueKp3sEYRYmxfC1d
QgwAn25H+NiWz9Sj5KLpVuaDPZ9lYlFw
=A+4C
-----END PGP SIGNATURE-----

review: Approve
Revision history for this message
Robert Collins (lifeless) wrote :

 +1

Revision history for this message
Andrew Bennetts (spiv) wrote :

John A Meinel wrote:
[...]
> This is starting to look like something that could benefit from
> OperationWithCleanups :)

Oh, definitely. Or perhaps Command objects should have an add_cleanup method
(and probably implement it using OperationWithCleanups)...

But this is a fix for the 2.0 branch, and I'm not sure we really want to
backport OperationWithCleanups to it and then rearrange things to use it on the
conservative, stable branch.

> So what this *breaks* is 'bzr log' with '-r branch:' (I believe),
> because -r branch:path acquires a write lock to fetch the remote
> revisions locally. Note that -r -1:path *doesn't* and refers to the same
> revision-id....
>
> That said, I think it is the right thing to do.

You're right that it does break that, although obviously '-r branch: FILE' was
already broken. I'm not sure what do about that, although perhaps guarding the
fetch in RevisionSpec_branch with "if revision_b not in
branch.repository.get_parent_map([revision_b]):" would help some cases. We
really want a way to upgrade a read lock to a write lock...

Anyway, that case doesn't appear to have test coverage, so it can't be *that*
important ;)

Thanks for the swift review, I'll send it to PQM now and file a bug about the
newly broken case.

-Andrew.

Preview Diff

[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
1=== modified file 'NEWS'
2--- NEWS 2009-10-28 01:16:10 +0000
3+++ NEWS 2009-10-29 05:53:10 +0000
4@@ -35,12 +35,18 @@
5 * Fix typos left after test_selftest refactoring.
6 (Vincent Ladeuil, Matt Nordhoff, #461149)
7
8+* Fixed ``ObjectNotLocked`` errors during ``bzr log -r NNN somefile``.
9+ (Andrew Bennetts, #445171)
10+
11 * PreviewTree file names are not limited by the encoding of the temp
12 directory's filesystem. (Aaron Bentley, #436794)
13
14 Improvements
15 ************
16
17+* ``bzr log`` now read-locks branches exactly once, so makes better use of
18+ data caches. (Andrew Bennetts)
19+
20 Documentation
21 *************
22
23
24=== modified file 'bzrlib/builtins.py'
25--- bzrlib/builtins.py 2009-09-10 06:32:42 +0000
26+++ bzrlib/builtins.py 2009-10-29 05:53:10 +0000
27@@ -2274,50 +2274,51 @@
28
29 file_ids = []
30 filter_by_dir = False
31- if file_list:
32- # find the file ids to log and check for directory filtering
33- b, file_info_list, rev1, rev2 = _get_info_for_log_files(revision,
34- file_list)
35- for relpath, file_id, kind in file_info_list:
36- if file_id is None:
37- raise errors.BzrCommandError(
38- "Path unknown at end or start of revision range: %s" %
39- relpath)
40- # If the relpath is the top of the tree, we log everything
41- if relpath == '':
42- file_ids = []
43- break
44+ b = None
45+ try:
46+ if file_list:
47+ # find the file ids to log and check for directory filtering
48+ b, file_info_list, rev1, rev2 = _get_info_for_log_files(
49+ revision, file_list)
50+ for relpath, file_id, kind in file_info_list:
51+ if file_id is None:
52+ raise errors.BzrCommandError(
53+ "Path unknown at end or start of revision range: %s" %
54+ relpath)
55+ # If the relpath is the top of the tree, we log everything
56+ if relpath == '':
57+ file_ids = []
58+ break
59+ else:
60+ file_ids.append(file_id)
61+ filter_by_dir = filter_by_dir or (
62+ kind in ['directory', 'tree-reference'])
63+ else:
64+ # log everything
65+ # FIXME ? log the current subdir only RBC 20060203
66+ if revision is not None \
67+ and len(revision) > 0 and revision[0].get_branch():
68+ location = revision[0].get_branch()
69 else:
70- file_ids.append(file_id)
71- filter_by_dir = filter_by_dir or (
72- kind in ['directory', 'tree-reference'])
73- else:
74- # log everything
75- # FIXME ? log the current subdir only RBC 20060203
76- if revision is not None \
77- and len(revision) > 0 and revision[0].get_branch():
78- location = revision[0].get_branch()
79- else:
80- location = '.'
81- dir, relpath = bzrdir.BzrDir.open_containing(location)
82- b = dir.open_branch()
83- rev1, rev2 = _get_revision_range(revision, b, self.name())
84-
85- # Decide on the type of delta & diff filtering to use
86- # TODO: add an --all-files option to make this configurable & consistent
87- if not verbose:
88- delta_type = None
89- else:
90- delta_type = 'full'
91- if not show_diff:
92- diff_type = None
93- elif file_ids:
94- diff_type = 'partial'
95- else:
96- diff_type = 'full'
97-
98- b.lock_read()
99- try:
100+ location = '.'
101+ dir, relpath = bzrdir.BzrDir.open_containing(location)
102+ b = dir.open_branch()
103+ b.lock_read()
104+ rev1, rev2 = _get_revision_range(revision, b, self.name())
105+
106+ # Decide on the type of delta & diff filtering to use
107+ # TODO: add an --all-files option to make this configurable & consistent
108+ if not verbose:
109+ delta_type = None
110+ else:
111+ delta_type = 'full'
112+ if not show_diff:
113+ diff_type = None
114+ elif file_ids:
115+ diff_type = 'partial'
116+ else:
117+ diff_type = 'full'
118+
119 # Build the log formatter
120 if log_format is None:
121 log_format = log.log_formatter_registry.get_default(b)
122@@ -2353,7 +2354,8 @@
123 diff_type=diff_type, _match_using_deltas=match_using_deltas)
124 Logger(b, rqst).show(lf)
125 finally:
126- b.unlock()
127+ if b is not None:
128+ b.unlock()
129
130
131 def _get_revision_range(revisionspec_list, branch, command_name):
132@@ -2423,10 +2425,15 @@
133 @display_command
134 def run(self, filename):
135 tree, relpath = WorkingTree.open_containing(filename)
136+ file_id = tree.path2id(relpath)
137 b = tree.branch
138- file_id = tree.path2id(relpath)
139- for revno, revision_id, what in log.find_touching_revisions(b, file_id):
140- self.outf.write("%6d %s\n" % (revno, what))
141+ b.lock_read()
142+ try:
143+ touching_revs = log.find_touching_revisions(b, file_id)
144+ for revno, revision_id, what in touching_revs:
145+ self.outf.write("%6d %s\n" % (revno, what))
146+ finally:
147+ b.unlock()
148
149
150 class cmd_ls(Command):
151
152=== modified file 'bzrlib/log.py'
153--- bzrlib/log.py 2009-06-10 03:56:49 +0000
154+++ bzrlib/log.py 2009-10-29 05:53:10 +0000
155@@ -1846,9 +1846,11 @@
156 :return: (branch, info_list, start_rev_info, end_rev_info) where
157 info_list is a list of (relative_path, file_id, kind) tuples where
158 kind is one of values 'directory', 'file', 'symlink', 'tree-reference'.
159+ branch will be read-locked.
160 """
161 from builtins import _get_revision_range, safe_relpath_files
162 tree, b, path = bzrdir.BzrDir.open_containing_tree_or_branch(file_list[0])
163+ b.lock_read()
164 # XXX: It's damn messy converting a list of paths to relative paths when
165 # those paths might be deleted ones, they might be on a case-insensitive
166 # filesystem and/or they might be in silly locations (like another branch).
167
168=== modified file 'bzrlib/merge_directive.py'
169--- bzrlib/merge_directive.py 2009-05-11 19:11:14 +0000
170+++ bzrlib/merge_directive.py 2009-10-29 05:53:10 +0000
171@@ -582,11 +582,13 @@
172 revision_id):
173 raise errors.PublicBranchOutOfDate(public_branch,
174 revision_id)
175+ testament_sha1 = t.as_sha1()
176 finally:
177 for entry in reversed(locked):
178 entry.unlock()
179- return klass(revision_id, t.as_sha1(), time, timezone, target_branch,
180- patch, public_branch, message, bundle, base_revision_id)
181+ return klass(revision_id, testament_sha1, time, timezone,
182+ target_branch, patch, public_branch, message, bundle,
183+ base_revision_id)
184
185 def _verify_patch(self, repository):
186 calculated_patch = self._generate_diff(repository, self.revision_id,
187
188=== modified file 'bzrlib/tests/__init__.py'
189--- bzrlib/tests/__init__.py 2009-08-24 20:30:18 +0000
190+++ bzrlib/tests/__init__.py 2009-10-29 05:53:10 +0000
191@@ -52,6 +52,7 @@
192 from bzrlib import (
193 branchbuilder,
194 bzrdir,
195+ chk_map,
196 debug,
197 errors,
198 hooks,
199@@ -1617,6 +1618,9 @@
200
201 def _run_bzr_core(self, args, retcode, encoding, stdin,
202 working_dir):
203+ # Clear chk_map page cache, because the contents are likely to mask
204+ # locking errors.
205+ chk_map.clear_cache()
206 if encoding is None:
207 encoding = osutils.get_user_encoding()
208 stdout = StringIOWrapper()

Subscribers

People subscribed via source and target branches