Merge lp:~jameinel/bzr/2.0.4-autopack-rename-507557 into lp:bzr/2.0
- 2.0.4-autopack-rename-507557
- Merge into 2.0
Status: | Merged |
---|---|
Approved by: | Vincent Ladeuil |
Approved revision: | no longer in the source branch. |
Merged at revision: | not available |
Proposed branch: | lp:~jameinel/bzr/2.0.4-autopack-rename-507557 |
Merge into: | lp:bzr/2.0 |
Diff against target: |
358 lines (+166/-26) 4 files modified
NEWS (+7/-0) bzrlib/repofmt/groupcompress_repo.py (+6/-5) bzrlib/repofmt/pack_repo.py (+57/-19) bzrlib/tests/test_repository.py (+96/-2) |
To merge this branch: | bzr merge lp:~jameinel/bzr/2.0.4-autopack-rename-507557 |
Related bugs: |
Reviewer | Review Type | Date Requested | Status |
---|---|---|---|
Vincent Ladeuil | Approve | ||
Robert Collins (community) | based on your description alone. | Approve | |
Review via email: mp+17778@code.launchpad.net |
Commit message
Description of the change
John A Meinel (jameinel) wrote : | # |
Robert Collins (lifeless) wrote : | # |
review: +1 based on your description alone.
-Rob
Vincent Ladeuil (vila) wrote : | # |
The fix is clearly demonstrated by the associated tests which makes
it a good candidate even for 2.0.5.
We may want some staging period so I think release *candidate* is good for that.
Gareth White (gwhite-deactivatedaccount) wrote : | # |
(This isn't a review, just some comments from an interested third party...)
* I've done some user-level testing with your fixes and they do appear to resolve the original bug. When stress-testing Bazaar my commits now all succeed, aside from the occasional instance of bug 507553 ;)
* I would suggest making the warnings into mutters simply because (a) they're not something the user should be alarmed about, and (b) when you get them it can be quite spammy (e.g. 6 files per revision = 54 warnings in the worst case). However, see next point.
* I noticed that groupcompress_
* I manually applied the above changes and repeated the stress test, and didn't get a warning at all. However, it did appear to be leaving around some pack files in the "packs" directory. I suspect this is because the single-revision pack that's created during a commit just before an autopack never gets added to pack-names. So the "if o.name not in already_obsolete and o.name in orig_disk_names" test fails.
* You've probably already fixed it locally, but I noticed there's still some debugging code in reload_pack_names() ;)
Gareth White (gwhite-deactivatedaccount) wrote : | # |
As a customer it would be nice to have this in 2.0.x but since 2.1 is so close I wouldn't be so worried about it.
John A Meinel (jameinel) wrote : | # |
-----BEGIN PGP SIGNED MESSAGE-----
Hash: SHA1
Gareth White wrote:
> (This isn't a review, just some comments from an interested third party...)
> * I've done some user-level testing with your fixes and they do appear to resolve the original bug. When stress-testing Bazaar my commits now all succeed, aside from the occasional instance of bug 507553 ;)
>
> * I would suggest making the warnings into mutters simply because (a) they're not something the user should be alarmed about, and (b) when you get them it can be quite spammy (e.g. 6 files per revision = 54 warnings in the worst case). However, see next point.
>
> * I noticed that groupcompress_
Nice catch.
>
> * I manually applied the above changes and repeated the stress test, and didn't get a warning at all. However, it did appear to be leaving around some pack files in the "packs" directory. I suspect this is because the single-revision pack that's created during a commit just before an autopack never gets added to pack-names. So the "if o.name not in already_obsolete and o.name in orig_disk_names" test fails.
I'm a little concerned about this one. Partially because my direct
testing didn't show this behavior. Leaving around an extra pack file on
every autopack would be bad. Or does this only happen if there is
contention?
>
> * You've probably already fixed it locally, but I noticed there's still some debugging code in reload_pack_names() ;)
Removed. I'm surprised Vila didn't catch it, he's usually pretty good
about spotting my 'pdb' lines. :)
John
=:->
-----BEGIN PGP SIGNATURE-----
Version: GnuPG v1.4.9 (Cygwin)
Comment: Using GnuPG with Mozilla - http://
iEYEARECAAYFAkt
3iMAoNn6O9sZu77
=haMF
-----END PGP SIGNATURE-----
John A Meinel (jameinel) wrote : | # |
-----BEGIN PGP SIGNED MESSAGE-----
Hash: SHA1
>> * I manually applied the above changes and repeated the stress test, and didn't get a warning at all. However, it did appear to be leaving around some pack files in the "packs" directory. I suspect this is because the single-revision pack that's created during a commit just before an autopack never gets added to pack-names. So the "if o.name not in already_obsolete and o.name in orig_disk_names" test fails.
>
> I'm a little concerned about this one. Partially because my direct
> testing didn't show this behavior. Leaving around an extra pack file on
> every autopack would be bad. Or does this only happen if there is
> contention?
>
I've confirmed this (with tests that fail), and I'm in the process of
adding a localized test for this behavior. There have been enough rough
spots that I'll probably just land this for 2.1.0rc1 to start with, and
then keep the patch around for future 2.0.5 once we've had some time
with it.
John
=:->
-----BEGIN PGP SIGNATURE-----
Version: GnuPG v1.4.9 (Cygwin)
Comment: Using GnuPG with Mozilla - http://
iEYEARECAAYFAkt
7JYAoLuasGQV3kl
=PrPm
-----END PGP SIGNATURE-----
John A Meinel (jameinel) wrote : | # |
-----BEGIN PGP SIGNED MESSAGE-----
Hash: SHA1
John A Meinel wrote:
>
>>> * I manually applied the above changes and repeated the stress test, and didn't get a warning at all. However, it did appear to be leaving around some pack files in the "packs" directory. I suspect this is because the single-revision pack that's created during a commit just before an autopack never gets added to pack-names. So the "if o.name not in already_obsolete and o.name in orig_disk_names" test fails.
>> I'm a little concerned about this one. Partially because my direct
>> testing didn't show this behavior. Leaving around an extra pack file on
>> every autopack would be bad. Or does this only happen if there is
>> contention?
>
>
> I've confirmed this (with tests that fail), and I'm in the process of
> adding a localized test for this behavior. There have been enough rough
> spots that I'll probably just land this for 2.1.0rc1 to start with, and
> then keep the patch around for future 2.0.5 once we've had some time
> with it.
>
> John
> =:->
Here is a possible fix. I don't really like the "isinstance()" check,
but it does fit. (If a 'pack' is a new then it isn't going to be in the
pack-names file, etc.)
The only other problem is that I think a pack stays "NewPack" between
multiple calls to .commit_
ExistingPack after commit_write_group finishes. (It could be done in
synchronize_
I'm tempted to just get rid of that check, and go ahead and let both
clients try to rename all obsolete packs (except for ones it has already
found in the target). And then downgrade the 'warning' to 'mutter'.
John
=:->
-----BEGIN PGP SIGNATURE-----
Version: GnuPG v1.4.9 (Cygwin)
Comment: Using GnuPG with Mozilla - http://
iEYEARECAAYFAkt
PnoAoIRfPd52mUO
=opCG
-----END PGP SIGNATURE-----
Robert Collins (lifeless) wrote : | # |
On Thu, 2010-01-21 at 19:18 +0000, John A Meinel wrote:
>
>
> I'm tempted to just get rid of that check, and go ahead and let both
> clients try to rename all obsolete packs (except for ones it has
> already
> found in the target). And then downgrade the 'warning' to 'mutter'.
+1 on this.
-Rob
Ian Clatworthy (ian-clatworthy) wrote : | # |
Is this fully approved now (given Rob's vote) or do you need a review still?
John A Meinel (jameinel) wrote : | # |
-----BEGIN PGP SIGNED MESSAGE-----
Hash: SHA1
Ian Clatworthy wrote:
> Is this fully approved now (given Rob's vote) or do you need a review still?
I think this is fully approved (but not marked as such?) I landed it in
2.1 rather than trying to squeeze it into the 2.0.4 series. (There was a
fair number of bumps to this, and I didn't want to push it just before
release.)
It has gotten a bit of a back burner, but I'm not sure right now whether
I want to push for 2.0.5, or just forget it and just have it be in 2.1.
John
=:->
-----BEGIN PGP SIGNATURE-----
Version: GnuPG v1.4.9 (Cygwin)
Comment: Using GnuPG with Mozilla - http://
iEYEARECAAYFAkt
VPEAn2miqkg2rmg
=Y0r5
-----END PGP SIGNATURE-----
Ian Clatworthy (ian-clatworthy) wrote : | # |
> It has gotten a bit of a back burner, but I'm not sure right now whether
> I want to push for 2.0.5, or just forget it and just have it be in 2.1.
The linked bug has Low priority (fwiw) and Gareth seems ok with it being in 2.1 only. At this point, I'd be tempted to leave it out of the 2.0 series.
Vincent Ladeuil (vila) wrote : | # |
John, did you land this patch into 2.1 ? 2.0 ? Dev ?
I'd be inclined to land it into 2.1 only (and to dev from there)
but since you're the RM, it's up to you.
John A Meinel (jameinel) wrote : | # |
-----BEGIN PGP SIGNED MESSAGE-----
Hash: SHA1
Vincent Ladeuil wrote:
> John, did you land this patch into 2.1 ? 2.0 ? Dev ?
>
> I'd be inclined to land it into 2.1 only (and to dev from there)
> but since you're the RM, it's up to you.
It has landed in 2.1 (a while ago), but since it was originally proposed
for 2.0 it stays around until we land it there. We could mark it as
merged if you find it is in your way, though since we are considering it
for a 2.0.5 inclusion, I didn't want it completely gone.
John
=:->
-----BEGIN PGP SIGNATURE-----
Version: GnuPG v1.4.9 (Cygwin)
Comment: Using GnuPG with Mozilla - http://
iEYEARECAAYFAkt
d1sAoJ3TSLD24X3
=xXYZ
-----END PGP SIGNATURE-----
Martin Pool (mbp) wrote : | # |
John, I think you should either land this, or supersede it if we actually don't care about putting it into 2.0. Given as Ian says the bug is assessed as low and the patch is not trivial, I would probably lean towards not landing it, but either is fine.
I don't want us getting to tolerate a long review queue because other stuff will get lost.
Vincent Ladeuil (vila) wrote : | # |
Marking merged since 2.1 is out.
I think we should focus on pushing 2.1 everywhere instead of doing a 2.0.5.
Robert Collins (lifeless) wrote : | # |
On Mon, 2010-03-01 at 08:04 +0000, Vincent Ladeuil wrote:
> Marking merged since 2.1 is out.
> I think we should focus on pushing 2.1 everywhere instead of doing a 2.0.5.
We're very unlikely to get 2.1 into karmic/hardy etc. Just saying :)
-Rob
Martin Pool (mbp) wrote : | # |
On 1 March 2010 19:24, Robert Collins <email address hidden> wrote:
> On Mon, 2010-03-01 at 08:04 +0000, Vincent Ladeuil wrote:
>> Marking merged since 2.1 is out.
>> I think we should focus on pushing 2.1 everywhere instead of doing a 2.0.5.
>
> We're very unlikely to get 2.1 into karmic/hardy etc. Just saying :)
Yes, 2.0 is still very much alive, and should get all patches that
make sense to fix there. However, if the bug is not severe or the fix
is not trivial, it's ok to leave it out.
Generally if the same patch will apply to both 2.1 and 2.0 it should
be merged to 2.0. 2.0 doesn't have a higher bar than 2.1.
--
Martin <http://
Vincent Ladeuil (vila) wrote : | # |
Yeah, digging while you were commenting :) I realized we already have quite a bit of mess between the various patches already landed in the 2.0 and 2.1 branches, I'm cleaning up and will land this patch in the 2.0 branch before merging into 2.1 and then into bzr.dev.
Preview Diff
1 | === modified file 'NEWS' |
2 | --- NEWS 2010-01-17 11:11:34 +0000 |
3 | +++ NEWS 2010-01-21 19:29:09 +0000 |
4 | @@ -38,6 +38,13 @@ |
5 | returns ``EINTR`` by calling ``PyErr_CheckSignals``. This affected the |
6 | optional ``_readdir_pyx`` extension. (Andrew Bennetts, #495023) |
7 | |
8 | +* Concurrent autopacking is more resilient to already-renamed pack files. |
9 | + If we find that a file we are about to obsolete is already obsoleted, we |
10 | + do not try to rename it, and we leave the file in ``obsolete_packs``. |
11 | + The code is also fault tolerant if a file goes missing, assuming that |
12 | + another process already removed the file. |
13 | + (John Arbash Meinel, Gareth White, #507557) |
14 | + |
15 | * Concurrent autopacks will no longer lose a newly created pack file. |
16 | There was a race condition, where if the reload happened at the right |
17 | time, the second packer would forget the name of the newly added pack |
18 | |
19 | === modified file 'bzrlib/repofmt/groupcompress_repo.py' |
20 | --- bzrlib/repofmt/groupcompress_repo.py 2009-12-16 20:20:04 +0000 |
21 | +++ bzrlib/repofmt/groupcompress_repo.py 2010-01-21 19:29:09 +0000 |
22 | @@ -1,4 +1,4 @@ |
23 | -# Copyright (C) 2008, 2009 Canonical Ltd |
24 | +# Copyright (C) 2008, 2009, 2010 Canonical Ltd |
25 | # |
26 | # This program is free software; you can redistribute it and/or modify |
27 | # it under the terms of the GNU General Public License as published by |
28 | @@ -704,10 +704,11 @@ |
29 | self._remove_pack_from_memory(pack) |
30 | # record the newly available packs and stop advertising the old |
31 | # packs |
32 | - result = self._save_pack_names(clear_obsolete_packs=True) |
33 | - # Move the old packs out of the way now they are no longer referenced. |
34 | - for revision_count, packs in pack_operations: |
35 | - self._obsolete_packs(packs) |
36 | + to_be_obsoleted = [] |
37 | + for _, packs in pack_operations: |
38 | + to_be_obsoleted.extend(packs) |
39 | + result = self._save_pack_names(clear_obsolete_packs=True, |
40 | + obsolete_packs=to_be_obsoleted) |
41 | return result |
42 | |
43 | |
44 | |
45 | === modified file 'bzrlib/repofmt/pack_repo.py' |
46 | --- bzrlib/repofmt/pack_repo.py 2010-01-16 20:46:03 +0000 |
47 | +++ bzrlib/repofmt/pack_repo.py 2010-01-21 19:29:09 +0000 |
48 | @@ -1541,10 +1541,11 @@ |
49 | self._remove_pack_from_memory(pack) |
50 | # record the newly available packs and stop advertising the old |
51 | # packs |
52 | - result = self._save_pack_names(clear_obsolete_packs=True) |
53 | - # Move the old packs out of the way now they are no longer referenced. |
54 | - for revision_count, packs in pack_operations: |
55 | - self._obsolete_packs(packs) |
56 | + to_be_obsoleted = [] |
57 | + for _, packs in pack_operations: |
58 | + to_be_obsoleted.extend(packs) |
59 | + result = self._save_pack_names(clear_obsolete_packs=True, |
60 | + obsolete_packs=to_be_obsoleted) |
61 | return result |
62 | |
63 | def _flush_new_pack(self): |
64 | @@ -1784,8 +1785,13 @@ |
65 | :param return: None. |
66 | """ |
67 | for pack in packs: |
68 | - pack.pack_transport.rename(pack.file_name(), |
69 | - '../obsolete_packs/' + pack.file_name()) |
70 | + try: |
71 | + pack.pack_transport.rename(pack.file_name(), |
72 | + '../obsolete_packs/' + pack.file_name()) |
73 | + except (errors.PathError, errors.TransportError), e: |
74 | + # TODO: Should these be warnings or mutters? |
75 | + mutter("couldn't rename obsolete pack, skipping it:\n%s" |
76 | + % (e,)) |
77 | # TODO: Probably needs to know all possible indices for this pack |
78 | # - or maybe list the directory and move all indices matching this |
79 | # name whether we recognize it or not? |
80 | @@ -1793,8 +1799,12 @@ |
81 | if self.chk_index is not None: |
82 | suffixes.append('.cix') |
83 | for suffix in suffixes: |
84 | - self._index_transport.rename(pack.name + suffix, |
85 | - '../obsolete_packs/' + pack.name + suffix) |
86 | + try: |
87 | + self._index_transport.rename(pack.name + suffix, |
88 | + '../obsolete_packs/' + pack.name + suffix) |
89 | + except (errors.PathError, errors.TransportError), e: |
90 | + mutter("couldn't rename obsolete index, skipping it:\n%s" |
91 | + % (e,)) |
92 | |
93 | def pack_distribution(self, total_revisions): |
94 | """Generate a list of the number of revisions to put in each pack. |
95 | @@ -1872,6 +1882,7 @@ |
96 | disk_nodes = set() |
97 | for index, key, value in self._iter_disk_pack_index(): |
98 | disk_nodes.add((key, value)) |
99 | + orig_disk_nodes = set(disk_nodes) |
100 | |
101 | # do a two-way diff against our original content |
102 | current_nodes = set() |
103 | @@ -1890,7 +1901,7 @@ |
104 | disk_nodes.difference_update(deleted_nodes) |
105 | disk_nodes.update(new_nodes) |
106 | |
107 | - return disk_nodes, deleted_nodes, new_nodes |
108 | + return disk_nodes, deleted_nodes, new_nodes, orig_disk_nodes |
109 | |
110 | def _syncronize_pack_names_from_disk_nodes(self, disk_nodes): |
111 | """Given the correct set of pack files, update our saved info. |
112 | @@ -1936,7 +1947,7 @@ |
113 | added.append(name) |
114 | return removed, added, modified |
115 | |
116 | - def _save_pack_names(self, clear_obsolete_packs=False): |
117 | + def _save_pack_names(self, clear_obsolete_packs=False, obsolete_packs=None): |
118 | """Save the list of packs. |
119 | |
120 | This will take out the mutex around the pack names list for the |
121 | @@ -1946,12 +1957,16 @@ |
122 | |
123 | :param clear_obsolete_packs: If True, clear out the contents of the |
124 | obsolete_packs directory. |
125 | + :param obsolete_packs: Packs that are obsolete once the new pack-names |
126 | + file has been written. |
127 | :return: A list of the names saved that were not previously on disk. |
128 | """ |
129 | + already_obsolete = [] |
130 | self.lock_names() |
131 | try: |
132 | builder = self._index_builder_class() |
133 | - disk_nodes, deleted_nodes, new_nodes = self._diff_pack_names() |
134 | + (disk_nodes, deleted_nodes, new_nodes, |
135 | + orig_disk_nodes) = self._diff_pack_names() |
136 | # TODO: handle same-name, index-size-changes here - |
137 | # e.g. use the value from disk, not ours, *unless* we're the one |
138 | # changing it. |
139 | @@ -1959,14 +1974,25 @@ |
140 | builder.add_node(key, value) |
141 | self.transport.put_file('pack-names', builder.finish(), |
142 | mode=self.repo.bzrdir._get_file_mode()) |
143 | - # move the baseline forward |
144 | self._packs_at_load = disk_nodes |
145 | if clear_obsolete_packs: |
146 | - self._clear_obsolete_packs() |
147 | + to_preserve = None |
148 | + if obsolete_packs: |
149 | + to_preserve = set([o.name for o in obsolete_packs]) |
150 | + already_obsolete = self._clear_obsolete_packs(to_preserve) |
151 | finally: |
152 | self._unlock_names() |
153 | # synchronise the memory packs list with what we just wrote: |
154 | self._syncronize_pack_names_from_disk_nodes(disk_nodes) |
155 | + if obsolete_packs: |
156 | + # TODO: We could add one more condition here. "if o.name not in |
157 | + # orig_disk_nodes and o != the new_pack we haven't written to |
158 | + # disk yet. However, the new pack object is not easily |
159 | + # accessible here (it would have to be passed through the |
160 | + # autopacking code, etc.) |
161 | + obsolete_packs = [o for o in obsolete_packs |
162 | + if o.name not in already_obsolete] |
163 | + self._obsolete_packs(obsolete_packs) |
164 | return [new_node[0][0] for new_node in new_nodes] |
165 | |
166 | def reload_pack_names(self): |
167 | @@ -1987,13 +2013,12 @@ |
168 | if first_read: |
169 | return True |
170 | # out the new value. |
171 | - disk_nodes, deleted_nodes, new_nodes = self._diff_pack_names() |
172 | + (disk_nodes, deleted_nodes, new_nodes, |
173 | + orig_disk_nodes) = self._diff_pack_names() |
174 | # _packs_at_load is meant to be the explicit list of names in |
175 | # 'pack-names' at then start. As such, it should not contain any |
176 | # pending names that haven't been written out yet. |
177 | - pack_names_nodes = disk_nodes.difference(new_nodes) |
178 | - pack_names_nodes.update(deleted_nodes) |
179 | - self._packs_at_load = pack_names_nodes |
180 | + self._packs_at_load = orig_disk_nodes |
181 | (removed, added, |
182 | modified) = self._syncronize_pack_names_from_disk_nodes(disk_nodes) |
183 | if removed or added or modified: |
184 | @@ -2008,15 +2033,28 @@ |
185 | raise |
186 | raise errors.RetryAutopack(self.repo, False, sys.exc_info()) |
187 | |
188 | - def _clear_obsolete_packs(self): |
189 | + def _clear_obsolete_packs(self, preserve=None): |
190 | """Delete everything from the obsolete-packs directory. |
191 | + |
192 | + :return: A list of pack identifiers (the filename without '.pack') that |
193 | + were found in obsolete_packs. |
194 | """ |
195 | + found = [] |
196 | obsolete_pack_transport = self.transport.clone('obsolete_packs') |
197 | + if preserve is None: |
198 | + preserve = set() |
199 | for filename in obsolete_pack_transport.list_dir('.'): |
200 | + name, ext = osutils.splitext(filename) |
201 | + if ext == '.pack': |
202 | + found.append(name) |
203 | + if name in preserve: |
204 | + continue |
205 | try: |
206 | obsolete_pack_transport.delete(filename) |
207 | except (errors.PathError, errors.TransportError), e: |
208 | - warning("couldn't delete obsolete pack, skipping it:\n%s" % (e,)) |
209 | + warning("couldn't delete obsolete pack, skipping it:\n%s" |
210 | + % (e,)) |
211 | + return found |
212 | |
213 | def _start_write_group(self): |
214 | # Do not permit preparation for writing if we're not in a 'write lock'. |
215 | |
216 | === modified file 'bzrlib/tests/test_repository.py' |
217 | --- bzrlib/tests/test_repository.py 2010-01-16 20:46:03 +0000 |
218 | +++ bzrlib/tests/test_repository.py 2010-01-21 19:29:09 +0000 |
219 | @@ -1082,6 +1082,31 @@ |
220 | packs.ensure_loaded() |
221 | return tree, r, packs, [rev1, rev2, rev3] |
222 | |
223 | + def test__clear_obsolete_packs(self): |
224 | + packs = self.get_packs() |
225 | + obsolete_pack_trans = packs.transport.clone('obsolete_packs') |
226 | + obsolete_pack_trans.put_bytes('a-pack.pack', 'content\n') |
227 | + obsolete_pack_trans.put_bytes('a-pack.rix', 'content\n') |
228 | + obsolete_pack_trans.put_bytes('a-pack.iix', 'content\n') |
229 | + obsolete_pack_trans.put_bytes('another-pack.pack', 'foo\n') |
230 | + obsolete_pack_trans.put_bytes('not-a-pack.rix', 'foo\n') |
231 | + res = packs._clear_obsolete_packs() |
232 | + self.assertEqual(['a-pack', 'another-pack'], sorted(res)) |
233 | + self.assertEqual([], obsolete_pack_trans.list_dir('.')) |
234 | + |
235 | + def test__clear_obsolete_packs_preserve(self): |
236 | + packs = self.get_packs() |
237 | + obsolete_pack_trans = packs.transport.clone('obsolete_packs') |
238 | + obsolete_pack_trans.put_bytes('a-pack.pack', 'content\n') |
239 | + obsolete_pack_trans.put_bytes('a-pack.rix', 'content\n') |
240 | + obsolete_pack_trans.put_bytes('a-pack.iix', 'content\n') |
241 | + obsolete_pack_trans.put_bytes('another-pack.pack', 'foo\n') |
242 | + obsolete_pack_trans.put_bytes('not-a-pack.rix', 'foo\n') |
243 | + res = packs._clear_obsolete_packs(preserve=set(['a-pack'])) |
244 | + self.assertEqual(['a-pack', 'another-pack'], sorted(res)) |
245 | + self.assertEqual(['a-pack.iix', 'a-pack.pack', 'a-pack.rix'], |
246 | + sorted(obsolete_pack_trans.list_dir('.'))) |
247 | + |
248 | def test__max_pack_count(self): |
249 | """The maximum pack count is a function of the number of revisions.""" |
250 | # no revisions - one pack, so that we can have a revision free repo |
251 | @@ -1107,6 +1132,28 @@ |
252 | # check some arbitrary big numbers |
253 | self.assertEqual(25, packs._max_pack_count(112894)) |
254 | |
255 | + def test__obsolete_packs(self): |
256 | + tree, r, packs, revs = self.make_packs_and_alt_repo(write_lock=True) |
257 | + names = packs.names() |
258 | + pack = packs.get_pack_by_name(names[0]) |
259 | + # Schedule this one for removal |
260 | + packs._remove_pack_from_memory(pack) |
261 | + # Simulate a concurrent update by renaming the .pack file and one of |
262 | + # the indices |
263 | + packs.transport.rename('packs/%s.pack' % (names[0],), |
264 | + 'obsolete_packs/%s.pack' % (names[0],)) |
265 | + packs.transport.rename('indices/%s.iix' % (names[0],), |
266 | + 'obsolete_packs/%s.iix' % (names[0],)) |
267 | + # Now trigger the obsoletion, and ensure that all the remaining files |
268 | + # are still renamed |
269 | + packs._obsolete_packs([pack]) |
270 | + self.assertEqual([n + '.pack' for n in names[1:]], |
271 | + sorted(packs._pack_transport.list_dir('.'))) |
272 | + # names[0] should not be present in the index anymore |
273 | + self.assertEqual(names[1:], |
274 | + sorted(set([osutils.splitext(n)[0] for n in |
275 | + packs._index_transport.list_dir('.')]))) |
276 | + |
277 | def test_pack_distribution_zero(self): |
278 | packs = self.get_packs() |
279 | self.assertEqual([0], packs.pack_distribution(0)) |
280 | @@ -1300,7 +1347,7 @@ |
281 | removed_pack = packs.get_pack_by_name(to_remove_name) |
282 | packs._remove_pack_from_memory(removed_pack) |
283 | names = packs.names() |
284 | - all_nodes, deleted_nodes, new_nodes = packs._diff_pack_names() |
285 | + all_nodes, deleted_nodes, new_nodes, _ = packs._diff_pack_names() |
286 | new_names = set([x[0][0] for x in new_nodes]) |
287 | self.assertEqual(names, sorted([x[0][0] for x in all_nodes])) |
288 | self.assertEqual(set(names) - set(orig_names), new_names) |
289 | @@ -1311,7 +1358,7 @@ |
290 | reloaded_names = packs.names() |
291 | self.assertEqual(orig_at_load, packs._packs_at_load) |
292 | self.assertEqual(names, reloaded_names) |
293 | - all_nodes, deleted_nodes, new_nodes = packs._diff_pack_names() |
294 | + all_nodes, deleted_nodes, new_nodes, _ = packs._diff_pack_names() |
295 | new_names = set([x[0][0] for x in new_nodes]) |
296 | self.assertEqual(names, sorted([x[0][0] for x in all_nodes])) |
297 | self.assertEqual(set(names) - set(orig_names), new_names) |
298 | @@ -1319,6 +1366,21 @@ |
299 | self.assertEqual([to_remove_name], |
300 | sorted([x[0][0] for x in deleted_nodes])) |
301 | |
302 | + def test_autopack_obsoletes_new_pack(self): |
303 | + tree, r, packs, revs = self.make_packs_and_alt_repo(write_lock=True) |
304 | + packs._max_pack_count = lambda x: 1 |
305 | + packs.pack_distribution = lambda x: [10] |
306 | + r.start_write_group() |
307 | + r.revisions.insert_record_stream([versionedfile.FulltextContentFactory( |
308 | + ('bogus-rev',), (), None, 'bogus-content\n')]) |
309 | + # This should trigger an autopack, which will combine everything into a |
310 | + # single pack file. |
311 | + new_names = r.commit_write_group() |
312 | + names = packs.names() |
313 | + self.assertEqual(1, len(names)) |
314 | + self.assertEqual([names[0] + '.pack'], |
315 | + packs._pack_transport.list_dir('.')) |
316 | + |
317 | def test_autopack_reloads_and_stops(self): |
318 | tree, r, packs, revs = self.make_packs_and_alt_repo(write_lock=True) |
319 | # After we have determined what needs to be autopacked, trigger a |
320 | @@ -1336,6 +1398,38 @@ |
321 | self.assertEqual(tree.branch.repository._pack_collection.names(), |
322 | packs.names()) |
323 | |
324 | + def test__save_pack_names(self): |
325 | + tree, r, packs, revs = self.make_packs_and_alt_repo(write_lock=True) |
326 | + names = packs.names() |
327 | + pack = packs.get_pack_by_name(names[0]) |
328 | + packs._remove_pack_from_memory(pack) |
329 | + packs._save_pack_names(obsolete_packs=[pack]) |
330 | + cur_packs = packs._pack_transport.list_dir('.') |
331 | + self.assertEqual([n + '.pack' for n in names[1:]], sorted(cur_packs)) |
332 | + # obsolete_packs will also have stuff like .rix and .iix present. |
333 | + obsolete_packs = packs.transport.list_dir('obsolete_packs') |
334 | + obsolete_names = set([osutils.splitext(n)[0] for n in obsolete_packs]) |
335 | + self.assertEqual([pack.name], sorted(obsolete_names)) |
336 | + |
337 | + def test__save_pack_names_already_obsoleted(self): |
338 | + tree, r, packs, revs = self.make_packs_and_alt_repo(write_lock=True) |
339 | + names = packs.names() |
340 | + pack = packs.get_pack_by_name(names[0]) |
341 | + packs._remove_pack_from_memory(pack) |
342 | + # We are going to simulate a concurrent autopack by manually obsoleting |
343 | + # the pack directly. |
344 | + packs._obsolete_packs([pack]) |
345 | + packs._save_pack_names(clear_obsolete_packs=True, |
346 | + obsolete_packs=[pack]) |
347 | + cur_packs = packs._pack_transport.list_dir('.') |
348 | + self.assertEqual([n + '.pack' for n in names[1:]], sorted(cur_packs)) |
349 | + # Note that while we set clear_obsolete_packs=True, it should not |
350 | + # delete a pack file that we have also scheduled for obsoletion. |
351 | + obsolete_packs = packs.transport.list_dir('obsolete_packs') |
352 | + obsolete_names = set([osutils.splitext(n)[0] for n in obsolete_packs]) |
353 | + self.assertEqual([pack.name], sorted(obsolete_names)) |
354 | + |
355 | + |
356 | |
357 | class TestPack(TestCaseWithTransport): |
358 | """Tests for the Pack object.""" |
I'm pretty happy with the result, but I'm not 100% sure this is a small enough change to go into 2.0.*
Anyway, this changes a few bits of the autopacking code.
1) When renaming a file into 'obsolete_packs', if we get an error, just continue on. We will generate a warning, but it shouldn't interrupt the process (it was already removed from the list of referenced files.)
2) When deleting files from 'obsolete_packs' don't delete files that we currently have scheduled for obsoletion. "If I'm scheduling it 'right now' then probably so did someone else, and they beat me to it."
3) For files found in (2) don't try to rename them (since we know they are already in obsolete_packs).
4) Go one step further. If a file we are about to obsolete has already been removed from 'pack-names', then we can assume that someone else has already scheduled its obsoletion and will be doing the rename instead of us.
This should fix most of the problems in bug #507557. I don't know if it also solves some of the other autopack concurrency issues, but it should at least help with that one.