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