Merge lp:~pbeaman/akiban-persistit/fix-1058254-join-corrupts-keys into lp:akiban-persistit
- fix-1058254-join-corrupts-keys
- Merge into trunk
Status: | Merged |
---|---|
Approved by: | Peter Beaman |
Approved revision: | 375 |
Merged at revision: | 373 |
Proposed branch: | lp:~pbeaman/akiban-persistit/fix-1058254-join-corrupts-keys |
Merge into: | lp:akiban-persistit |
Diff against target: |
527 lines (+353/-21) 9 files modified
src/main/java/com/persistit/Buffer.java (+19/-1) src/main/java/com/persistit/Exchange.java (+2/-0) src/main/java/com/persistit/MVV.java (+2/-2) src/main/java/com/persistit/VolumeStructure.java (+1/-2) src/main/java/com/persistit/util/SequencerConstants.java (+10/-0) src/main/java/com/persistit/util/ThreadSequencer.java (+17/-0) src/test/java/com/persistit/Bug1022567Test.java (+235/-0) src/test/java/com/persistit/Bug1041003Test.java (+8/-16) src/test/java/com/persistit/Bug1058254Test.java (+59/-0) |
To merge this branch: | bzr merge lp:~pbeaman/akiban-persistit/fix-1058254-join-corrupts-keys |
Related bugs: |
Reviewer | Review Type | Date Requested | Status |
---|---|---|---|
Akiban Build User | Needs Fixing | ||
Nathan Williams | Approve | ||
Review via email: mp+127075@code.launchpad.net |
Commit message
Description of the change
Bug fixes for 1058254 Join on range delete can change successor keys and 1022567 Stress test failure in deallocateGarba
The primary change is in the calculation of newEbc in Buffer#join. The original code ignored the possibility if page A and C are being joined where A->B and B->C, then the ebc for some key on page B could be less than the value computed by joinMeasure. That case is now handled and a small unit test is provided to exercise that code.
Since 1058254 was found while hunting 1022567, and because the fix for 1022567 involves only the removal of a condition in the assert statement that fired, that change is also included in this branch. However, note that the unit test for this is a bit tricky and requires an enhancement of the ThreadSequencer code to permit a condition to be tested.
Peter Beaman (pbeaman) wrote : | # |
I added an explanation to the original bug at https:/
Nathan Williams (nwilliams) wrote : | # |
Thanks for the explanation. Makes sense to me.
Akiban Build User (build-akiban) wrote : | # |
There were 2 failures during build/test:
* job system-tests-mtr failed at build number 2585: http://
* view must-pass failed: system-tests-mtr is red
Peter Beaman (pbeaman) wrote : | # |
Dopey problem. Re-approving.
Preview Diff
1 | === modified file 'src/main/java/com/persistit/Buffer.java' |
2 | --- src/main/java/com/persistit/Buffer.java 2012-09-27 18:33:06 +0000 |
3 | +++ src/main/java/com/persistit/Buffer.java 2012-09-28 22:19:23 +0000 |
4 | @@ -2448,7 +2448,25 @@ |
5 | final long measureRight = buffer.joinMeasure(KEY_BLOCK_START, foundAt2); |
6 | kbData = buffer.getInt(foundAt2); |
7 | final int oldEbc = decodeKeyBlockEbc(kbData); |
8 | - final int newEbc = Math.min(oldEbc, Math.min((int) (measureLeft >>> 32), (int) (measureRight >>> 32))); |
9 | + |
10 | + final int newEbc; |
11 | + if (_rightSibling == buffer._page) { |
12 | + /* |
13 | + * Pages are are contiguous so first key of right sibling is |
14 | + * identical to max key of the current page. |
15 | + */ |
16 | + newEbc = Math.min(oldEbc, Math.min((int) (measureLeft >>> 32), (int) (measureRight >>> 32))); |
17 | + } else { |
18 | + /* |
19 | + * Since pages are not contiguous, we need to extract the max key |
20 | + * from the current page and compute the ebc from it. |
21 | + */ |
22 | + keyAt(foundAt1, indexKey); |
23 | + final int firstUnique = indexKey.firstUniqueByteIndex(spareKey); |
24 | + newEbc = Math.min(Math.min(oldEbc, firstUnique), |
25 | + Math.min((int) (measureLeft >>> 32), (int) (measureRight >>> 32))); |
26 | + } |
27 | + |
28 | tail = decodeKeyBlockTail(kbData); |
29 | tbData = buffer.getInt(tail); |
30 | |
31 | |
32 | === modified file 'src/main/java/com/persistit/Exchange.java' |
33 | --- src/main/java/com/persistit/Exchange.java 2012-09-27 18:23:17 +0000 |
34 | +++ src/main/java/com/persistit/Exchange.java 2012-09-28 22:19:23 +0000 |
35 | @@ -33,6 +33,7 @@ |
36 | import static com.persistit.Key.LTEQ; |
37 | import static com.persistit.Key.RIGHT_GUARD_KEY; |
38 | import static com.persistit.Key.maxStorableKeySize; |
39 | +import static com.persistit.util.SequencerConstants.DEALLOCATE_CHAIN_A; |
40 | import static com.persistit.util.SequencerConstants.WRITE_WRITE_STORE_A; |
41 | import static com.persistit.util.ThreadSequencer.sequence; |
42 | |
43 | @@ -3523,6 +3524,7 @@ |
44 | left = lc._deallocLeftPage; |
45 | right = lc._deallocRightPage; |
46 | if (left != 0) { |
47 | + sequence(DEALLOCATE_CHAIN_A); |
48 | _volume.getStructure().deallocateGarbageChain(left, right); |
49 | lc._deallocLeftPage = 0; |
50 | lc._deallocRightPage = 0; |
51 | |
52 | === modified file 'src/main/java/com/persistit/MVV.java' |
53 | --- src/main/java/com/persistit/MVV.java 2012-09-27 22:17:34 +0000 |
54 | +++ src/main/java/com/persistit/MVV.java 2012-09-28 22:19:23 +0000 |
55 | @@ -316,7 +316,7 @@ |
56 | */ |
57 | to++; |
58 | int next = to; |
59 | - |
60 | + |
61 | while (next < end) { |
62 | final long curVersion = getVersion(target, to); |
63 | final int vlength = getLength(target, to); |
64 | @@ -342,7 +342,7 @@ |
65 | next = to; |
66 | } |
67 | } else if (curVersion > versionHandle) { |
68 | - if (vh2ts(versionHandle) != vh2ts(curVersion)) { |
69 | + if (vh2ts(versionHandle) != vh2ts(curVersion)) { |
70 | throw new IllegalStateException("Versions out of order"); |
71 | } |
72 | remainder = end - to; |
73 | |
74 | === modified file 'src/main/java/com/persistit/VolumeStructure.java' |
75 | --- src/main/java/com/persistit/VolumeStructure.java 2012-09-27 18:22:25 +0000 |
76 | +++ src/main/java/com/persistit/VolumeStructure.java 2012-09-28 22:19:23 +0000 |
77 | @@ -541,8 +541,7 @@ |
78 | try { |
79 | final long garbagePage = getGarbageRoot(); |
80 | if (garbagePage != 0) { |
81 | - if (left == garbagePage || right == garbagePage) { |
82 | - Debug.$assert0.t(false); |
83 | + if (left == garbagePage) { |
84 | throw new IllegalStateException("De-allocating page that is already garbage: " + "root=" |
85 | + garbagePage + " left=" + left + " right=" + right); |
86 | } |
87 | |
88 | === modified file 'src/main/java/com/persistit/util/SequencerConstants.java' |
89 | --- src/main/java/com/persistit/util/SequencerConstants.java 2012-08-24 13:57:19 +0000 |
90 | +++ src/main/java/com/persistit/util/SequencerConstants.java 2012-09-28 22:19:23 +0000 |
91 | @@ -93,4 +93,14 @@ |
92 | int[][] LONG_RECORD_ALLOCATE_SCHEDULED = new int[][] { array(LONG_RECORD_ALLOCATE_B), |
93 | array(LONG_RECORD_ALLOCATE_A, LONG_RECORD_ALLOCATE_B) }; |
94 | |
95 | + /* |
96 | + * Used in testing delete/deallocate sequence in Bug1022567Test |
97 | + */ |
98 | + int DEALLOCATE_CHAIN_A = allocate("DEALLOCATE_CHAIN_A"); |
99 | + int DEALLOCATE_CHAIN_B = allocate("DEALLOCATE_CHAIN_B"); |
100 | + int DEALLOCATE_CHAIN_C = allocate("DEALLOCATE_CHAIN_C"); |
101 | + int[][] DEALLOCATE_CHAIN_SCHEDULED = new int[][] { array(DEALLOCATE_CHAIN_A, DEALLOCATE_CHAIN_B), |
102 | + array(DEALLOCATE_CHAIN_B), array(DEALLOCATE_CHAIN_A, DEALLOCATE_CHAIN_C), |
103 | + array(DEALLOCATE_CHAIN_A, DEALLOCATE_CHAIN_C) }; |
104 | + |
105 | } |
106 | |
107 | === modified file 'src/main/java/com/persistit/util/ThreadSequencer.java' |
108 | --- src/main/java/com/persistit/util/ThreadSequencer.java 2012-08-24 13:57:19 +0000 |
109 | +++ src/main/java/com/persistit/util/ThreadSequencer.java 2012-09-28 22:19:23 +0000 |
110 | @@ -110,8 +110,16 @@ |
111 | |
112 | private final static List<String> LOCATIONS = new ArrayList<String>(); |
113 | |
114 | + private final static List<Condition> CONDITIONS = new ArrayList<Condition>(); |
115 | + |
116 | private final static int MAX_LOCATIONS = 64; |
117 | |
118 | + public static class Condition { |
119 | + public boolean enabled() { |
120 | + return true; |
121 | + } |
122 | + } |
123 | + |
124 | public synchronized static int allocate(final String locationName) { |
125 | for (final String alreadyRegistered : LOCATIONS) { |
126 | assert !alreadyRegistered.equals(locationName) : "Location name " + locationName + " is already in use"; |
127 | @@ -119,6 +127,7 @@ |
128 | final int value = LOCATIONS.size(); |
129 | assert value < MAX_LOCATIONS : "Too many ThreadSequence locations"; |
130 | LOCATIONS.add(locationName); |
131 | + CONDITIONS.add(new Condition()); |
132 | return value; |
133 | } |
134 | |
135 | @@ -167,6 +176,10 @@ |
136 | } |
137 | } |
138 | |
139 | + public static void setCondition(final int location, final Condition condition) { |
140 | + CONDITIONS.set(location, condition); |
141 | + } |
142 | + |
143 | public static String sequencerHistory() { |
144 | return ENABLED_SEQUENCER.history(); |
145 | } |
146 | @@ -369,6 +382,10 @@ |
147 | assert location >= 0 && location < MAX_LOCATIONS : "Location must be between 0 and 63, inclusive"; |
148 | Semaphore semaphore = null; |
149 | |
150 | + if (!CONDITIONS.get(location).enabled()) { |
151 | + return; |
152 | + } |
153 | + |
154 | synchronized (this) { |
155 | if ((_enabled & (1L << location)) == 0) { |
156 | return; |
157 | |
158 | === added file 'src/test/java/com/persistit/Bug1022567Test.java' |
159 | --- src/test/java/com/persistit/Bug1022567Test.java 1970-01-01 00:00:00 +0000 |
160 | +++ src/test/java/com/persistit/Bug1022567Test.java 2012-09-28 22:19:23 +0000 |
161 | @@ -0,0 +1,235 @@ |
162 | +/** |
163 | + * Copyright © 2012 Akiban Technologies, Inc. All rights reserved. |
164 | + * |
165 | + * This program and the accompanying materials are made available |
166 | + * under the terms of the Eclipse Public License v1.0 which |
167 | + * accompanies this distribution, and is available at |
168 | + * http://www.eclipse.org/legal/epl-v10.html |
169 | + * |
170 | + * This program may also be available under different license terms. |
171 | + * For more information, see www.akiban.com or contact licensing@akiban.com. |
172 | + * |
173 | + * Contributors: |
174 | + * Akiban Technologies, Inc. |
175 | + */ |
176 | + |
177 | +package com.persistit; |
178 | + |
179 | +import static com.persistit.unit.UnitTestProperties.VOLUME_NAME; |
180 | +import static com.persistit.util.SequencerConstants.DEALLOCATE_CHAIN_A; |
181 | +import static com.persistit.util.SequencerConstants.DEALLOCATE_CHAIN_B; |
182 | +import static com.persistit.util.SequencerConstants.DEALLOCATE_CHAIN_C; |
183 | +import static com.persistit.util.SequencerConstants.DEALLOCATE_CHAIN_SCHEDULED; |
184 | +import static com.persistit.util.ThreadSequencer.addSchedules; |
185 | +import static com.persistit.util.ThreadSequencer.enableSequencer; |
186 | +import static com.persistit.util.ThreadSequencer.sequence; |
187 | +import static com.persistit.util.ThreadSequencer.setCondition; |
188 | + |
189 | +import java.util.ArrayList; |
190 | +import java.util.List; |
191 | +import java.util.Properties; |
192 | + |
193 | +import org.junit.Test; |
194 | + |
195 | +import com.persistit.exception.PersistitException; |
196 | +import com.persistit.unit.UnitTestProperties; |
197 | +import com.persistit.util.ThreadSequencer; |
198 | +import com.persistit.util.ThreadSequencer.Condition; |
199 | + |
200 | +/** |
201 | + * MixtureTxn1 suite failed with this Exception: |
202 | + * |
203 | + * Stress6 [Thread-195] FAILED [Thread-195]: java.lang.IllegalStateException: |
204 | + * De-allocating page that is already garbage: root=36689 left=72838 right=36689 |
205 | + * |
206 | + * at com.persistit.VolumeStructure.deallocateGarbageChain(VolumeStructure.java: |
207 | + * 510) |
208 | + * |
209 | + * As it turns out, the condition being asserted is a rare but legitimate state |
210 | + * and the bug fix it to remove a conditional clause from the assert statement |
211 | + * in VolumeStructure#deallocateGarbageChain. The recreate1022567 method below |
212 | + * reproduces the legitimate case, and without the change in VolumeStructure |
213 | + * cuases a the IllegalStateException. |
214 | + */ |
215 | + |
216 | +public class Bug1022567Test extends PersistitUnitTestCase { |
217 | + |
218 | + private final static String TREE_NAME = "Bug1022567"; |
219 | + |
220 | + private Exchange getExchange() throws PersistitException { |
221 | + return _persistit.getExchange(VOLUME_NAME, TREE_NAME, true); |
222 | + } |
223 | + |
224 | + @Override |
225 | + public Properties getProperties(final boolean cleanup) { |
226 | + return UnitTestProperties.getBiggerProperties(cleanup); |
227 | + } |
228 | + |
229 | + /** |
230 | + * This method carefully constructs a scenario in which the right sibling of |
231 | + * a chain being deallocated is the garbage root page. The reported bug is |
232 | + * that this condition causes an assert, when in fact the condition is rare |
233 | + * but legitimate. Prior to fixing the bug this method throws an |
234 | + * IllegalStateException. |
235 | + * |
236 | + * @throws Exception |
237 | + */ |
238 | + @Test |
239 | + public void recreate1022567() throws Exception { |
240 | + try { |
241 | + enableSequencer(true); |
242 | + addSchedules(DEALLOCATE_CHAIN_SCHEDULED); |
243 | + |
244 | + final long mainThreadId = Thread.currentThread().getId(); |
245 | + setCondition(DEALLOCATE_CHAIN_A, new Condition() { |
246 | + @Override |
247 | + public boolean enabled() { |
248 | + return Thread.currentThread().getId() == mainThreadId; |
249 | + } |
250 | + }); |
251 | + for (int loop = 0; loop < 1000; loop++) { |
252 | + _persistit.getVolume(VOLUME_NAME).truncate(); |
253 | + final Exchange ex1 = getExchange(); |
254 | + final List<Integer> keys = new ArrayList<Integer>(); |
255 | + /* |
256 | + * Lay down 4 pages |
257 | + */ |
258 | + long nextAvailable = 0; |
259 | + ex1.getValue().put(createString(2000)); |
260 | + for (int i = 0;; i++) { |
261 | + ex1.clear().append(i).store(); |
262 | + final long newNextAvailable = nextAvailablePage(); |
263 | + if (newNextAvailable != nextAvailable) { |
264 | + keys.add(i - 1); |
265 | + nextAvailable = newNextAvailable; |
266 | + if (keys.size() > 3) { |
267 | + break; |
268 | + } |
269 | + } |
270 | + } |
271 | + final Key key1 = new Key(_persistit); |
272 | + final Key key2 = new Key(_persistit); |
273 | + key1.to(keys.get(1)); |
274 | + key2.to(keys.get(3)); |
275 | + |
276 | + // Need to create a race: this thread needs to remove the inside |
277 | + // two |
278 | + // pages and then let the other thread proceed before |
279 | + // deallocating |
280 | + // them. |
281 | + |
282 | + final Thread t = new Thread(new Runnable() { |
283 | + @Override |
284 | + public void run() { |
285 | + try { |
286 | + sequence(DEALLOCATE_CHAIN_B); |
287 | + final Exchange ex2 = getExchange(); |
288 | + ex2.to(Key.AFTER); |
289 | + while (ex2.previous()) { |
290 | + ex2.remove(); |
291 | + } |
292 | + sequence(DEALLOCATE_CHAIN_C); |
293 | + } catch (final PersistitException e) { |
294 | + e.printStackTrace(); |
295 | + } |
296 | + } |
297 | + }); |
298 | + t.start(); |
299 | + key1.nudgeLeft(); |
300 | + key2.nudgeLeft(); |
301 | + ex1.removeKeyRange(key1, key2); |
302 | + t.join(); |
303 | + } |
304 | + } finally { |
305 | + ThreadSequencer.disableSequencer(); |
306 | + } |
307 | + } |
308 | + |
309 | + @Test |
310 | + public void deleteEnlargingRanges() throws Exception { |
311 | + final Exchange ex = getExchange(); |
312 | + final VolumeStructure vs = ex.getVolume().getStructure(); |
313 | + final List<KeyState> keys = new ArrayList<KeyState>(); |
314 | + /* |
315 | + * Lay down about 1000 pages |
316 | + */ |
317 | + long nextAvailable = 0; |
318 | + ex.getValue().put(RED_FOX); |
319 | + for (int i = 0;; i++) { |
320 | + ex.clear().append(i).store(); |
321 | + final long newNextAvailable = nextAvailablePage(); |
322 | + if (newNextAvailable != nextAvailable) { |
323 | + keys.add(new KeyState(ex.getKey())); |
324 | + nextAvailable = newNextAvailable; |
325 | + if (nextAvailable > 1000) { |
326 | + break; |
327 | + } |
328 | + } |
329 | + } |
330 | + final List<Thread> threads = new ArrayList<Thread>(); |
331 | + for (int i = 2; i < 480; i++) { |
332 | + final int offset = i; |
333 | + final Thread t = new Thread(new Runnable() { |
334 | + @Override |
335 | + public void run() { |
336 | + try { |
337 | + final Key key1 = new Key(_persistit); |
338 | + final Key key2 = new Key(_persistit); |
339 | + keys.get(500 - offset).copyTo(key1); |
340 | + keys.get(500 + offset).copyTo(key2); |
341 | + final Exchange exchange = _persistit.getExchange(VOLUME_NAME, TREE_NAME, false); |
342 | + exchange.removeKeyRange(key1, key2); |
343 | + } catch (final PersistitException e) { |
344 | + e.printStackTrace(); |
345 | + } |
346 | + } |
347 | + }); |
348 | + t.start(); |
349 | + threads.add(t); |
350 | + } |
351 | + for (final Thread t : threads) { |
352 | + t.join(); |
353 | + } |
354 | + } |
355 | + |
356 | + @Test |
357 | + public void deleteEnlargingRangesSingleThread() throws Exception { |
358 | + final Exchange ex = getExchange(); |
359 | + final VolumeStructure vs = ex.getVolume().getStructure(); |
360 | + final List<KeyState> keys = new ArrayList<KeyState>(); |
361 | + /* |
362 | + * Lay down about 1000 pages |
363 | + */ |
364 | + long nextAvailable = 0; |
365 | + ex.getValue().put(createString(2000)); |
366 | + for (int i = 0;; i++) { |
367 | + ex.clear().append(i).store(); |
368 | + final long newNextAvailable = nextAvailablePage(); |
369 | + if (newNextAvailable != nextAvailable) { |
370 | + keys.add(new KeyState(ex.getKey())); |
371 | + nextAvailable = newNextAvailable; |
372 | + if (nextAvailable > 1000) { |
373 | + break; |
374 | + } |
375 | + } |
376 | + } |
377 | + for (int i = 2; i < 80; i++) { |
378 | + final int offset = i; |
379 | + try { |
380 | + final Key key1 = new Key(_persistit); |
381 | + final Key key2 = new Key(_persistit); |
382 | + keys.get(500 - offset).copyTo(key1); |
383 | + keys.get(500 + offset).copyTo(key2); |
384 | + final Exchange exchange = _persistit.getExchange(VOLUME_NAME, TREE_NAME, false); |
385 | + exchange.removeKeyRange(key1, key2); |
386 | + } catch (final PersistitException e) { |
387 | + e.printStackTrace(); |
388 | + } |
389 | + } |
390 | + } |
391 | + |
392 | + private long nextAvailablePage() throws PersistitException { |
393 | + return _persistit.getVolume(VOLUME_NAME).getStorage().getNextAvailablePage(); |
394 | + } |
395 | + |
396 | +} |
397 | |
398 | === modified file 'src/test/java/com/persistit/Bug1041003Test.java' |
399 | --- src/test/java/com/persistit/Bug1041003Test.java 2012-09-26 21:30:03 +0000 |
400 | +++ src/test/java/com/persistit/Bug1041003Test.java 2012-09-28 22:19:23 +0000 |
401 | @@ -15,26 +15,17 @@ |
402 | |
403 | package com.persistit; |
404 | |
405 | -import static org.junit.Assert.assertEquals; |
406 | -import static org.junit.Assert.assertTrue; |
407 | import static org.junit.Assert.fail; |
408 | |
409 | -import java.io.File; |
410 | import java.io.IOException; |
411 | -import java.io.RandomAccessFile; |
412 | import java.nio.ByteBuffer; |
413 | import java.nio.channels.FileChannel; |
414 | import java.util.Properties; |
415 | -import java.util.Timer; |
416 | -import java.util.TimerTask; |
417 | |
418 | import org.junit.Test; |
419 | |
420 | import com.persistit.JournalRecord.JE; |
421 | import com.persistit.Transaction.CommitPolicy; |
422 | -import com.persistit.exception.CorruptJournalException; |
423 | -import com.persistit.exception.CorruptVolumeException; |
424 | -import com.persistit.exception.PersistitException; |
425 | import com.persistit.exception.PersistitIOException; |
426 | import com.persistit.unit.UnitTestProperties; |
427 | |
428 | @@ -74,15 +65,15 @@ |
429 | @Test |
430 | public void leaveTransactionBufferFlipped() throws Exception { |
431 | /* |
432 | - * Need first journal file almost full so that an attempt to write a transaction |
433 | - * will force a rollover. |
434 | + * Need first journal file almost full so that an attempt to write a |
435 | + * transaction will force a rollover. |
436 | */ |
437 | final Exchange exchange = _persistit.getExchange(_volumeName, "Bug1041003Test", true); |
438 | _persistit.flush(); |
439 | final JournalManager jman = _persistit.getJournalManager(); |
440 | - ByteBuffer bb = ByteBuffer.allocate(BLOCKSIZE); |
441 | - long size = BLOCKSIZE - JE.OVERHEAD - jman.getCurrentAddress() - 1; |
442 | - bb.position((int)(size - JournalRecord.TX.OVERHEAD)); |
443 | + final ByteBuffer bb = ByteBuffer.allocate(BLOCKSIZE); |
444 | + final long size = BLOCKSIZE - JE.OVERHEAD - jman.getCurrentAddress() - 1; |
445 | + bb.position((int) (size - JournalRecord.TX.OVERHEAD)); |
446 | jman.writeTransactionToJournal(bb, 1, 2, 0); |
447 | final Transaction txn = _persistit.getTransaction(); |
448 | final ErrorInjectingFileChannel eifc = errorInjectingChannel(_persistit.getJournalManager().getFileChannel( |
449 | @@ -115,11 +106,12 @@ |
450 | try { |
451 | exchange.getValue().put(RED_FOX + RED_FOX); |
452 | /* |
453 | - * Bug 1041003 causes the following line to throw an IllegalStateException. |
454 | + * Bug 1041003 causes the following line to throw an |
455 | + * IllegalStateException. |
456 | */ |
457 | exchange.to(1).store(); |
458 | txn.commit(CommitPolicy.HARD); |
459 | - } catch (IllegalStateException e) { |
460 | + } catch (final IllegalStateException e) { |
461 | fail("Bug 1041003 strikes: " + e); |
462 | } finally { |
463 | txn.end(); |
464 | |
465 | === added file 'src/test/java/com/persistit/Bug1058254Test.java' |
466 | --- src/test/java/com/persistit/Bug1058254Test.java 1970-01-01 00:00:00 +0000 |
467 | +++ src/test/java/com/persistit/Bug1058254Test.java 2012-09-28 22:19:23 +0000 |
468 | @@ -0,0 +1,59 @@ |
469 | +/** |
470 | + * Copyright © 2012 Akiban Technologies, Inc. All rights reserved. |
471 | + * |
472 | + * This program and the accompanying materials are made available |
473 | + * under the terms of the Eclipse Public License v1.0 which |
474 | + * accompanies this distribution, and is available at |
475 | + * http://www.eclipse.org/legal/epl-v10.html |
476 | + * |
477 | + * This program may also be available under different license terms. |
478 | + * For more information, see www.akiban.com or contact licensing@akiban.com. |
479 | + * |
480 | + * Contributors: |
481 | + * Akiban Technologies, Inc. |
482 | + */ |
483 | + |
484 | +package com.persistit; |
485 | + |
486 | +import static com.persistit.unit.UnitTestProperties.VOLUME_NAME; |
487 | +import static org.junit.Assert.assertEquals; |
488 | +import static org.junit.Assert.assertTrue; |
489 | + |
490 | +import org.junit.Test; |
491 | + |
492 | +import com.persistit.exception.PersistitException; |
493 | + |
494 | +/** |
495 | + * A range delete that crosses at least two page boundaries can write an |
496 | + * incorrect key when merging records; this causes it and subsequent keys within |
497 | + * the page to be wrong and also leads to a discontinuity between the max key in |
498 | + * the page and the first key of its right sibling. |
499 | + */ |
500 | + |
501 | +public class Bug1058254Test extends PersistitUnitTestCase { |
502 | + |
503 | + private final static String TREE_NAME = "Bug1022567"; |
504 | + |
505 | + private Exchange getExchange() throws PersistitException { |
506 | + return _persistit.getExchange(VOLUME_NAME, TREE_NAME, true); |
507 | + } |
508 | + |
509 | + @Test |
510 | + public void joinDiscontinuity() throws Exception { |
511 | + final Exchange ex = getExchange(); |
512 | + ex.getValue().put(createString(2000)); |
513 | + for (int i = 3444; i < 3600; i++) { |
514 | + ex.to(i).store(); |
515 | + } |
516 | + final Key key1 = new Key(_persistit); |
517 | + final Key key2 = new Key(_persistit); |
518 | + key1.to(3445); |
519 | + key2.to(3557); |
520 | + ex.removeKeyRange(key1, key2); |
521 | + ex.to(Key.BEFORE); |
522 | + assertTrue(ex.next()); |
523 | + assertEquals(3444, ex.getKey().decodeInt()); |
524 | + assertTrue(ex.next()); |
525 | + assertEquals(3557, ex.getKey().decodeInt()); |
526 | + } |
527 | +} |
I know I asked you last week but have forgotten. Why is it legitimate for the right pointer to already be the garbage root?
This looks good otherwise I think.