##// END OF EJS Templates
revlog: test possible read race condition with splitting...
marmoute -
r51239:b0cdd0be stable
parent child Browse files
Show More
@@ -1,335 +1,394 b''
1 Test correctness of revlog inline -> non-inline transition
1 Test correctness of revlog inline -> non-inline transition
2 ----------------------------------------------------------
2 ----------------------------------------------------------
3
3
4 Helper extension to intercept renames and kill process
4 Helper extension to intercept renames and kill process
5
5
6 $ cat > $TESTTMP/intercept_before_rename.py << EOF
6 $ cat > $TESTTMP/intercept_before_rename.py << EOF
7 > import os
7 > import os
8 > import signal
8 > import signal
9 > from mercurial import extensions, util
9 > from mercurial import extensions, util
10 >
10 >
11 > def extsetup(ui):
11 > def extsetup(ui):
12 > def close(orig, *args, **kwargs):
12 > def close(orig, *args, **kwargs):
13 > path = util.normpath(args[0]._atomictempfile__name)
13 > path = util.normpath(args[0]._atomictempfile__name)
14 > if path.endswith(b'/.hg/store/data/file.i'):
14 > if path.endswith(b'/.hg/store/data/file.i'):
15 > os.kill(os.getpid(), signal.SIGKILL)
15 > os.kill(os.getpid(), signal.SIGKILL)
16 > return orig(*args, **kwargs)
16 > return orig(*args, **kwargs)
17 > extensions.wrapfunction(util.atomictempfile, 'close', close)
17 > extensions.wrapfunction(util.atomictempfile, 'close', close)
18 > EOF
18 > EOF
19
19
20 $ cat > $TESTTMP/intercept_after_rename.py << EOF
20 $ cat > $TESTTMP/intercept_after_rename.py << EOF
21 > import os
21 > import os
22 > import signal
22 > import signal
23 > from mercurial import extensions, util
23 > from mercurial import extensions, util
24 >
24 >
25 > def extsetup(ui):
25 > def extsetup(ui):
26 > def close(orig, *args, **kwargs):
26 > def close(orig, *args, **kwargs):
27 > path = util.normpath(args[0]._atomictempfile__name)
27 > path = util.normpath(args[0]._atomictempfile__name)
28 > r = orig(*args, **kwargs)
28 > r = orig(*args, **kwargs)
29 > if path.endswith(b'/.hg/store/data/file.i'):
29 > if path.endswith(b'/.hg/store/data/file.i'):
30 > os.kill(os.getpid(), signal.SIGKILL)
30 > os.kill(os.getpid(), signal.SIGKILL)
31 > return r
31 > return r
32 > extensions.wrapfunction(util.atomictempfile, 'close', close)
32 > extensions.wrapfunction(util.atomictempfile, 'close', close)
33 > EOF
33 > EOF
34
34
35 $ cat > $TESTTMP/killme.py << EOF
35 $ cat > $TESTTMP/killme.py << EOF
36 > import os
36 > import os
37 > import signal
37 > import signal
38 >
38 >
39 > def killme(ui, repo, hooktype, **kwargs):
39 > def killme(ui, repo, hooktype, **kwargs):
40 > os.kill(os.getpid(), signal.SIGKILL)
40 > os.kill(os.getpid(), signal.SIGKILL)
41 > EOF
41 > EOF
42
42
43 $ cat > $TESTTMP/reader_wait_split.py << EOF
44 > import os
45 > import signal
46 > from mercurial import extensions, revlog, testing
47 > def _wait_post_load(orig, self, *args, **kwargs):
48 > wait = b'data/file' in self.radix
49 > if wait:
50 > testing.wait_file(b"$TESTTMP/writer-revlog-split")
51 > r = orig(self, *args, **kwargs)
52 > if wait:
53 > testing.write_file(b"$TESTTMP/reader-index-read")
54 > testing.wait_file(b"$TESTTMP/writer-revlog-unsplit")
55 > return r
56 >
57 > def extsetup(ui):
58 > extensions.wrapfunction(revlog.revlog, '_loadindex', _wait_post_load)
59 > EOF
60
43 setup a repository for tests
61 setup a repository for tests
44 ----------------------------
62 ----------------------------
45
63
46 $ cat >> $HGRCPATH << EOF
64 $ cat >> $HGRCPATH << EOF
47 > [format]
65 > [format]
48 > revlog-compression=none
66 > revlog-compression=none
49 > EOF
67 > EOF
50
68
51 $ hg init troffset-computation
69 $ hg init troffset-computation
52 $ cd troffset-computation
70 $ cd troffset-computation
53 $ printf '%20d' '1' > file
71 $ printf '%20d' '1' > file
54 $ hg commit -Aqma
72 $ hg commit -Aqma
55 $ printf '%1024d' '1' > file
73 $ printf '%1024d' '1' > file
56 $ hg commit -Aqmb
74 $ hg commit -Aqmb
57 $ printf '%20d' '1' > file
75 $ printf '%20d' '1' > file
58 $ hg commit -Aqmc
76 $ hg commit -Aqmc
59 $ dd if=/dev/zero of=file bs=1k count=128 > /dev/null 2>&1
77 $ dd if=/dev/zero of=file bs=1k count=128 > /dev/null 2>&1
60 $ hg commit -AqmD
78 $ hg commit -AqmD
61
79
62 Reference size:
80 Reference size:
63 $ f -s file
81 $ f -s file
64 file: size=131072
82 file: size=131072
65 $ f -s .hg/store/data/file*
83 $ f -s .hg/store/data/file*
66 .hg/store/data/file.d: size=132139
84 .hg/store/data/file.d: size=132139
67 .hg/store/data/file.i: size=256
85 .hg/store/data/file.i: size=256
68
86
69 $ cd ..
87 $ cd ..
70
88
71
89
72 Test a hard crash after the file was split but before the transaction was committed
90 Test a hard crash after the file was split but before the transaction was committed
73 ===================================================================================
91 ===================================================================================
74
92
75 Test offset computation to correctly factor in the index entries themselves.
93 Test offset computation to correctly factor in the index entries themselves.
76 Also test that the new data size has the correct size if the transaction is aborted
94 Also test that the new data size has the correct size if the transaction is aborted
77 after the index has been replaced.
95 after the index has been replaced.
78
96
79 Test repo has commits a, b, c, D, where D is large (grows the revlog enough that it
97 Test repo has commits a, b, c, D, where D is large (grows the revlog enough that it
80 transitions to non-inline storage). The clone initially has changes a, b
98 transitions to non-inline storage). The clone initially has changes a, b
81 and will transition to non-inline storage when adding c, D.
99 and will transition to non-inline storage when adding c, D.
82
100
83 If the transaction adding c, D is rolled back, then we don't undo the revlog split,
101 If the transaction adding c, D is rolled back, then we don't undo the revlog split,
84 but truncate the index and the data to remove both c and D.
102 but truncate the index and the data to remove both c and D.
85
103
86
104
87 $ hg clone --quiet --rev 1 troffset-computation troffset-computation-copy
105 $ hg clone --quiet --rev 1 troffset-computation troffset-computation-copy
88 $ cd troffset-computation-copy
106 $ cd troffset-computation-copy
89
107
90 Reference size:
108 Reference size:
91 $ f -s file
109 $ f -s file
92 file: size=1024
110 file: size=1024
93 $ f -s .hg/store/data/file*
111 $ f -s .hg/store/data/file*
94 .hg/store/data/file.i: size=1174
112 .hg/store/data/file.i: size=1174
95
113
96 $ cat > .hg/hgrc <<EOF
114 $ cat > .hg/hgrc <<EOF
97 > [hooks]
115 > [hooks]
98 > pretxnchangegroup = python:$TESTTMP/killme.py:killme
116 > pretxnchangegroup = python:$TESTTMP/killme.py:killme
99 > EOF
117 > EOF
100 #if chg
118 #if chg
101 $ hg pull ../troffset-computation
119 $ hg pull ../troffset-computation
102 pulling from ../troffset-computation
120 pulling from ../troffset-computation
103 [255]
121 [255]
104 #else
122 #else
105 $ hg pull ../troffset-computation
123 $ hg pull ../troffset-computation
106 pulling from ../troffset-computation
124 pulling from ../troffset-computation
107 Killed
125 Killed
108 [137]
126 [137]
109 #endif
127 #endif
110
128
111
129
112 The revlog have been split on disk
130 The revlog have been split on disk
113
131
114 $ f -s .hg/store/data/file*
132 $ f -s .hg/store/data/file*
115 .hg/store/data/file.d: size=132139
133 .hg/store/data/file.d: size=132139
116 .hg/store/data/file.i: size=256
134 .hg/store/data/file.i: size=256
117
135
118 $ cat .hg/store/journal | tr -s '\000' ' ' | grep data/file | tail -1
136 $ cat .hg/store/journal | tr -s '\000' ' ' | grep data/file | tail -1
119 data/file.i 128
137 data/file.i 128
120
138
121 The first file.i entry should match the "Reference size" above.
139 The first file.i entry should match the "Reference size" above.
122 The first file.d entry is the temporary record during the split,
140 The first file.d entry is the temporary record during the split,
123
141
124 The second entry after the split happened. The sum of the second file.d
142 The second entry after the split happened. The sum of the second file.d
125 and the second file.i entry should match the first file.i entry.
143 and the second file.i entry should match the first file.i entry.
126
144
127 $ cat .hg/store/journal | tr -s '\000' ' ' | grep data/file
145 $ cat .hg/store/journal | tr -s '\000' ' ' | grep data/file
128 data/file.i 1174
146 data/file.i 1174
129 data/file.d 0
147 data/file.d 0
130 data/file.d 1046
148 data/file.d 1046
131 data/file.i 128
149 data/file.i 128
132 $ hg recover
150 $ hg recover
133 rolling back interrupted transaction
151 rolling back interrupted transaction
134 (verify step skipped, run `hg verify` to check your repository content)
152 (verify step skipped, run `hg verify` to check your repository content)
135 $ f -s .hg/store/data/file*
153 $ f -s .hg/store/data/file*
136 .hg/store/data/file.d: size=1046
154 .hg/store/data/file.d: size=1046
137 .hg/store/data/file.i: size=128
155 .hg/store/data/file.i: size=128
138 $ hg tip
156 $ hg tip
139 changeset: 1:cfa8d6e60429
157 changeset: 1:cfa8d6e60429
140 tag: tip
158 tag: tip
141 user: test
159 user: test
142 date: Thu Jan 01 00:00:00 1970 +0000
160 date: Thu Jan 01 00:00:00 1970 +0000
143 summary: b
161 summary: b
144
162
145 $ hg verify -q
163 $ hg verify -q
146 warning: revlog 'data/file.d' not in fncache!
164 warning: revlog 'data/file.d' not in fncache!
147 1 warnings encountered!
165 1 warnings encountered!
148 hint: run "hg debugrebuildfncache" to recover from corrupt fncache
166 hint: run "hg debugrebuildfncache" to recover from corrupt fncache
149 $ hg debugrebuildfncache --only-data
167 $ hg debugrebuildfncache --only-data
150 adding data/file.d
168 adding data/file.d
151 1 items added, 0 removed from fncache
169 1 items added, 0 removed from fncache
152 $ hg verify -q
170 $ hg verify -q
153 $ cd ..
171 $ cd ..
154
172
155 Test a hard crash right before the index is move into place
173 Test a hard crash right before the index is move into place
156 ===========================================================
174 ===========================================================
157
175
158 Now retry the procedure but intercept the rename of the index and check that
176 Now retry the procedure but intercept the rename of the index and check that
159 the journal does not contain the new index size. This demonstrates the edge case
177 the journal does not contain the new index size. This demonstrates the edge case
160 where the data file is left as garbage.
178 where the data file is left as garbage.
161
179
162 $ hg clone --quiet --rev 1 troffset-computation troffset-computation-copy2
180 $ hg clone --quiet --rev 1 troffset-computation troffset-computation-copy2
163 $ cd troffset-computation-copy2
181 $ cd troffset-computation-copy2
164
182
165 Reference size:
183 Reference size:
166 $ f -s file
184 $ f -s file
167 file: size=1024
185 file: size=1024
168 $ f -s .hg/store/data/file*
186 $ f -s .hg/store/data/file*
169 .hg/store/data/file.i: size=1174
187 .hg/store/data/file.i: size=1174
170
188
171 $ cat > .hg/hgrc <<EOF
189 $ cat > .hg/hgrc <<EOF
172 > [extensions]
190 > [extensions]
173 > intercept_rename = $TESTTMP/intercept_before_rename.py
191 > intercept_rename = $TESTTMP/intercept_before_rename.py
174 > [hooks]
192 > [hooks]
175 > pretxnchangegroup = python:$TESTTMP/killme.py:killme
193 > pretxnchangegroup = python:$TESTTMP/killme.py:killme
176 > EOF
194 > EOF
177 #if chg
195 #if chg
178 $ hg pull ../troffset-computation
196 $ hg pull ../troffset-computation
179 pulling from ../troffset-computation
197 pulling from ../troffset-computation
180 [255]
198 [255]
181 #else
199 #else
182 $ hg pull ../troffset-computation
200 $ hg pull ../troffset-computation
183 pulling from ../troffset-computation
201 pulling from ../troffset-computation
184 Killed
202 Killed
185 [137]
203 [137]
186 #endif
204 #endif
187
205
188 The data file is created, but the revlog is still inline
206 The data file is created, but the revlog is still inline
189
207
190 $ f -s .hg/store/data/file*
208 $ f -s .hg/store/data/file*
191 .hg/store/data/file.d: size=132139
209 .hg/store/data/file.d: size=132139
192 .hg/store/data/file.i: size=132395
210 .hg/store/data/file.i: size=132395
193
211
194 $ cat .hg/store/journal | tr -s '\000' ' ' | grep data/file
212 $ cat .hg/store/journal | tr -s '\000' ' ' | grep data/file
195 data/file.i 1174
213 data/file.i 1174
196 data/file.d 0
214 data/file.d 0
197 data/file.d 1046
215 data/file.d 1046
198
216
199 $ hg recover
217 $ hg recover
200 rolling back interrupted transaction
218 rolling back interrupted transaction
201 (verify step skipped, run `hg verify` to check your repository content)
219 (verify step skipped, run `hg verify` to check your repository content)
202 $ f -s .hg/store/data/file*
220 $ f -s .hg/store/data/file*
203 .hg/store/data/file.d: size=1046
221 .hg/store/data/file.d: size=1046
204 .hg/store/data/file.i: size=1174
222 .hg/store/data/file.i: size=1174
205 $ hg tip
223 $ hg tip
206 changeset: 1:cfa8d6e60429
224 changeset: 1:cfa8d6e60429
207 tag: tip
225 tag: tip
208 user: test
226 user: test
209 date: Thu Jan 01 00:00:00 1970 +0000
227 date: Thu Jan 01 00:00:00 1970 +0000
210 summary: b
228 summary: b
211
229
212 $ hg verify -q
230 $ hg verify -q
213 $ cd ..
231 $ cd ..
214
232
215 Test a hard crash right after the index is move into place
233 Test a hard crash right after the index is move into place
216 ===========================================================
234 ===========================================================
217
235
218 Now retry the procedure but intercept the rename of the index.
236 Now retry the procedure but intercept the rename of the index.
219
237
220 Things get corrupted /o\
238 Things get corrupted /o\
221
239
222 $ hg clone --quiet --rev 1 troffset-computation troffset-computation-crash-after-rename
240 $ hg clone --quiet --rev 1 troffset-computation troffset-computation-crash-after-rename
223 $ cd troffset-computation-crash-after-rename
241 $ cd troffset-computation-crash-after-rename
224
242
225 Reference size:
243 Reference size:
226 $ f -s file
244 $ f -s file
227 file: size=1024
245 file: size=1024
228 $ f -s .hg/store/data/file*
246 $ f -s .hg/store/data/file*
229 .hg/store/data/file.i: size=1174
247 .hg/store/data/file.i: size=1174
230
248
231 $ cat > .hg/hgrc <<EOF
249 $ cat > .hg/hgrc <<EOF
232 > [extensions]
250 > [extensions]
233 > intercept_rename = $TESTTMP/intercept_after_rename.py
251 > intercept_rename = $TESTTMP/intercept_after_rename.py
234 > [hooks]
252 > [hooks]
235 > pretxnchangegroup = python:$TESTTMP/killme.py:killme
253 > pretxnchangegroup = python:$TESTTMP/killme.py:killme
236 > EOF
254 > EOF
237 #if chg
255 #if chg
238 $ hg pull ../troffset-computation
256 $ hg pull ../troffset-computation
239 pulling from ../troffset-computation
257 pulling from ../troffset-computation
240 [255]
258 [255]
241 #else
259 #else
242 $ hg pull ../troffset-computation
260 $ hg pull ../troffset-computation
243 pulling from ../troffset-computation
261 pulling from ../troffset-computation
244 Killed
262 Killed
245 [137]
263 [137]
246 #endif
264 #endif
247
265
248 the revlog has been split on disk
266 the revlog has been split on disk
249
267
250 $ f -s .hg/store/data/file*
268 $ f -s .hg/store/data/file*
251 .hg/store/data/file.d: size=132139
269 .hg/store/data/file.d: size=132139
252 .hg/store/data/file.i: size=256
270 .hg/store/data/file.i: size=256
253
271
254 $ cat .hg/store/journal | tr -s '\000' ' ' | grep data/file
272 $ cat .hg/store/journal | tr -s '\000' ' ' | grep data/file
255 data/file.i 1174
273 data/file.i 1174
256 data/file.d 0
274 data/file.d 0
257 data/file.d 1046
275 data/file.d 1046
258
276
259 $ hg recover
277 $ hg recover
260 rolling back interrupted transaction
278 rolling back interrupted transaction
261 abort: attempted to truncate data/file.i to 1174 bytes, but it was already 256 bytes
279 abort: attempted to truncate data/file.i to 1174 bytes, but it was already 256 bytes
262
280
263 [255]
281 [255]
264 $ f -s .hg/store/data/file*
282 $ f -s .hg/store/data/file*
265 .hg/store/data/file.d: size=1046
283 .hg/store/data/file.d: size=1046
266 .hg/store/data/file.i: size=256
284 .hg/store/data/file.i: size=256
267 $ hg tip
285 $ hg tip
268 changeset: 1:cfa8d6e60429
286 changeset: 1:cfa8d6e60429
269 tag: tip
287 tag: tip
270 user: test
288 user: test
271 date: Thu Jan 01 00:00:00 1970 +0000
289 date: Thu Jan 01 00:00:00 1970 +0000
272 summary: b
290 summary: b
273
291
274 $ hg verify -q
292 $ hg verify -q
275 abandoned transaction found - run hg recover
293 abandoned transaction found - run hg recover
276 warning: revlog 'data/file.d' not in fncache!
294 warning: revlog 'data/file.d' not in fncache!
277 file@0: data length off by -131093 bytes
295 file@0: data length off by -131093 bytes
278 file@2: unpacking fa1120531cc1: partial read of revlog data/file.d; expected 21 bytes from offset 1046, got 0
296 file@2: unpacking fa1120531cc1: partial read of revlog data/file.d; expected 21 bytes from offset 1046, got 0
279 file@3: unpacking a631378adaa3: partial read of revlog data/file.d; expected 131072 bytes from offset 1067, got -21
297 file@3: unpacking a631378adaa3: partial read of revlog data/file.d; expected 131072 bytes from offset 1067, got -21
280 file@?: rev 2 points to nonexistent changeset 2
298 file@?: rev 2 points to nonexistent changeset 2
281 (expected )
299 (expected )
282 file@?: fa1120531cc1 not in manifests
300 file@?: fa1120531cc1 not in manifests
283 file@?: rev 3 points to nonexistent changeset 3
301 file@?: rev 3 points to nonexistent changeset 3
284 (expected )
302 (expected )
285 file@?: a631378adaa3 not in manifests
303 file@?: a631378adaa3 not in manifests
286 not checking dirstate because of previous errors
304 not checking dirstate because of previous errors
287 3 warnings encountered!
305 3 warnings encountered!
288 hint: run "hg debugrebuildfncache" to recover from corrupt fncache
306 hint: run "hg debugrebuildfncache" to recover from corrupt fncache
289 7 integrity errors encountered!
307 7 integrity errors encountered!
290 (first damaged changeset appears to be 0)
308 (first damaged changeset appears to be 0)
291 [1]
309 [1]
292 $ cd ..
310 $ cd ..
293
311
294 Have the transaction rollback itself without any hard crash
312 Have the transaction rollback itself without any hard crash
295 ===========================================================
313 ===========================================================
296
314
297
315
298 Repeat the original test but let hg rollback the transaction.
316 Repeat the original test but let hg rollback the transaction.
299
317
300 $ hg clone --quiet --rev 1 troffset-computation troffset-computation-copy-rb
318 $ hg clone --quiet --rev 1 troffset-computation troffset-computation-copy-rb
301 $ cd troffset-computation-copy-rb
319 $ cd troffset-computation-copy-rb
302 $ cat > .hg/hgrc <<EOF
320 $ cat > .hg/hgrc <<EOF
303 > [hooks]
321 > [hooks]
304 > pretxnchangegroup = false
322 > pretxnchangegroup = false
305 > EOF
323 > EOF
306 $ hg pull ../troffset-computation
324 $ hg pull ../troffset-computation
307 pulling from ../troffset-computation
325 pulling from ../troffset-computation
308 searching for changes
326 searching for changes
309 adding changesets
327 adding changesets
310 adding manifests
328 adding manifests
311 adding file changes
329 adding file changes
312 transaction abort!
330 transaction abort!
313 rollback completed
331 rollback completed
314 abort: pretxnchangegroup hook exited with status 1
332 abort: pretxnchangegroup hook exited with status 1
315 [40]
333 [40]
316
334
317 File are still split on disk, with the expected size.
335 File are still split on disk, with the expected size.
318
336
319 $ f -s .hg/store/data/file*
337 $ f -s .hg/store/data/file*
320 .hg/store/data/file.d: size=1046
338 .hg/store/data/file.d: size=1046
321 .hg/store/data/file.i: size=128
339 .hg/store/data/file.i: size=128
322
340
323 $ hg tip
341 $ hg tip
324 changeset: 1:cfa8d6e60429
342 changeset: 1:cfa8d6e60429
325 tag: tip
343 tag: tip
326 user: test
344 user: test
327 date: Thu Jan 01 00:00:00 1970 +0000
345 date: Thu Jan 01 00:00:00 1970 +0000
328 summary: b
346 summary: b
329
347
330 $ hg verify -q
348 $ hg verify -q
331 warning: revlog 'data/file.d' not in fncache!
349 warning: revlog 'data/file.d' not in fncache!
332 1 warnings encountered!
350 1 warnings encountered!
333 hint: run "hg debugrebuildfncache" to recover from corrupt fncache
351 hint: run "hg debugrebuildfncache" to recover from corrupt fncache
334 $ cd ..
352 $ cd ..
335
353
354 Read race
355 =========
356
357 We check that a client that started reading a revlog (its index) after the
358 split and end reading (the data) after the rollback should be fine
359
360 $ hg clone --quiet --rev 1 troffset-computation troffset-computation-race
361 $ cd troffset-computation-race
362 $ cat > .hg/hgrc <<EOF
363 > [hooks]
364 > pretxnchangegroup=$RUNTESTDIR/testlib/wait-on-file 5 $TESTTMP/reader-index-read $TESTTMP/writer-revlog-split
365 > pretxnclose = false
366 > EOF
367
368 start a reader
369
370 $ hg cat --rev 0 file \
371 > --config "extensions.wait_read=$TESTTMP/reader_wait_split.py" \
372 > 2> $TESTTMP/reader.stderr \
373 > > $TESTTMP/reader.stdout &
374
375 Do a failed pull in //
376
377 $ hg pull ../troffset-computation
378 pulling from ../troffset-computation
379 searching for changes
380 adding changesets
381 adding manifests
382 adding file changes
383 transaction abort!
384 rollback completed
385 abort: pretxnclose hook exited with status 1
386 [40]
387 $ touch $TESTTMP/writer-revlog-unsplit
388 $ wait
389
390 The reader should be fine
391 $ cat $TESTTMP/reader.stderr
392 $ cat $TESTTMP/reader.stdout
393 1 (no-eol)
394 $ cd ..
General Comments 0
You need to be logged in to leave comments. Login now