Merge lp:~nwilliams/akiban-persistit/fix_912514_fetchAndRemove_2 into lp:akiban-persistit
- fix_912514_fetchAndRemove_2
- Merge into trunk
Status: | Merged | ||||
---|---|---|---|---|---|
Approved by: | Peter Beaman | ||||
Approved revision: | 317 | ||||
Merged at revision: | 311 | ||||
Proposed branch: | lp:~nwilliams/akiban-persistit/fix_912514_fetchAndRemove_2 | ||||
Merge into: | lp:akiban-persistit | ||||
Prerequisite: | lp:~nwilliams/akiban-persistit/move-core-up | ||||
Diff against target: |
436 lines (+185/-56) 2 files modified
src/main/java/com/persistit/Exchange.java (+71/-56) src/test/java/com/persistit/Bug912514Test.java (+114/-0) |
||||
To merge this branch: | bzr merge lp:~nwilliams/akiban-persistit/fix_912514_fetchAndRemove_2 | ||||
Related bugs: |
|
Reviewer | Review Type | Date Requested | Status |
---|---|---|---|
Peter Beaman | Approve | ||
Review via email: mp+107863@code.launchpad.net |
This proposal supersedes a proposal from 2012-05-29.
Commit message
Description of the change
Fix Exchange store and fetchAndRemove to work correctly both inside and out of a transaction.
The vast majority of this is Peter's original branch. The last few commits were refactorings, to eliminate duplicate code, that I noticed were possible and suggested on the merge prop.
Original branch and description:
lp:~pbeaman/akiban-persistit/fix_912514_fetchAndRemove
The basic strategy to fix this bug is to used a the ThreadLocal-based cached Value object, rather than Exchange.
New test Bug912514 test tests some of the cases. I will also (and have not yet) run PersistitMapStr
Peter Beaman (pbeaman) wrote : | # |
Peter Beaman (pbeaman) : | # |
Preview Diff
1 | === modified file 'src/main/java/com/persistit/Exchange.java' | |||
2 | --- src/main/java/com/persistit/Exchange.java 2012-05-25 18:50:59 +0000 | |||
3 | +++ src/main/java/com/persistit/Exchange.java 2012-05-29 19:52:29 +0000 | |||
4 | @@ -1031,7 +1031,6 @@ | |||
5 | 1031 | * | 1031 | * |
6 | 1032 | * @return Encoded key location within the data page. The page itself is | 1032 | * @return Encoded key location within the data page. The page itself is |
7 | 1033 | * made valid in the level cache. | 1033 | * made valid in the level cache. |
8 | 1034 | * @throws PMapException | ||
9 | 1035 | */ | 1034 | */ |
10 | 1036 | private int search(Key key, boolean writer) throws PersistitException { | 1035 | private int search(Key key, boolean writer) throws PersistitException { |
11 | 1037 | Buffer buffer = null; | 1036 | Buffer buffer = null; |
12 | @@ -1099,7 +1098,6 @@ | |||
13 | 1099 | * | 1098 | * |
14 | 1100 | * @return Encoded key location within the level. The page itself is valid | 1099 | * @return Encoded key location within the level. The page itself is valid |
15 | 1101 | * within the level cache. | 1100 | * within the level cache. |
16 | 1102 | * @throws PMapException | ||
17 | 1103 | */ | 1101 | */ |
18 | 1104 | private int searchTree(Key key, int toLevel, boolean writer) throws PersistitException { | 1102 | private int searchTree(Key key, int toLevel, boolean writer) throws PersistitException { |
19 | 1105 | Buffer oldBuffer = null; | 1103 | Buffer oldBuffer = null; |
20 | @@ -1198,7 +1196,6 @@ | |||
21 | 1198 | * @param currentLevel | 1196 | * @param currentLevel |
22 | 1199 | * current level in the tree | 1197 | * current level in the tree |
23 | 1200 | * @return Encoded key location within the page. | 1198 | * @return Encoded key location within the page. |
24 | 1201 | * @throws PMapException | ||
25 | 1202 | */ | 1199 | */ |
26 | 1203 | private int searchLevel(Key key, boolean edge, long pageAddress, int currentLevel, boolean writer) | 1200 | private int searchLevel(Key key, boolean edge, long pageAddress, int currentLevel, boolean writer) |
27 | 1204 | throws PersistitException { | 1201 | throws PersistitException { |
28 | @@ -1323,15 +1320,9 @@ | |||
29 | 1323 | * uponError | 1320 | * uponError |
30 | 1324 | */ | 1321 | */ |
31 | 1325 | boolean storeInternal(Key key, Value value, int level, int options) throws PersistitException { | 1322 | boolean storeInternal(Key key, Value value, int level, int options) throws PersistitException { |
32 | 1326 | if ((options & StoreOptions.FETCH) > 0 && (options & StoreOptions.MVCC) > 0) { | ||
33 | 1327 | throw new IllegalArgumentException("Both fetch and MVCC not supported"); | ||
34 | 1328 | } | ||
35 | 1329 | 1323 | ||
36 | 1330 | final boolean doMVCC = (options & StoreOptions.MVCC) > 0; | 1324 | final boolean doMVCC = (options & StoreOptions.MVCC) > 0; |
41 | 1331 | final boolean doAnyFetch = (options & StoreOptions.FETCH) > 0 || doMVCC; | 1325 | final boolean doFetch = (options & StoreOptions.FETCH) > 0; |
38 | 1332 | |||
39 | 1333 | // spare used for fetch | ||
40 | 1334 | Debug.$assert0.t(!doAnyFetch || value != _spareValue); | ||
42 | 1335 | 1326 | ||
43 | 1336 | // spares used for new splits/levels | 1327 | // spares used for new splits/levels |
44 | 1337 | Debug.$assert0.t(key != _spareKey1); | 1328 | Debug.$assert0.t(key != _spareKey1); |
45 | @@ -1344,6 +1335,8 @@ | |||
46 | 1344 | boolean incrementMVVCount = false; | 1335 | boolean incrementMVVCount = false; |
47 | 1345 | 1336 | ||
48 | 1346 | final int maxSimpleValueSize = maxValueSize(key.getEncodedSize()); | 1337 | final int maxSimpleValueSize = maxValueSize(key.getEncodedSize()); |
49 | 1338 | final Value spareValue = _persistit.getThreadLocalValue(); | ||
50 | 1339 | assert !(doMVCC & value == spareValue || doFetch && value == _spareValue): "storeInternal may be use the supplied Value: " + value; | ||
51 | 1347 | 1340 | ||
52 | 1348 | // | 1341 | // |
53 | 1349 | // First insert the record in the data page | 1342 | // First insert the record in the data page |
54 | @@ -1369,7 +1362,7 @@ | |||
55 | 1369 | // This method may delay significantly for I/O and must | 1362 | // This method may delay significantly for I/O and must |
56 | 1370 | // be called when there are no other claimed resources. | 1363 | // be called when there are no other claimed resources. |
57 | 1371 | // | 1364 | // |
59 | 1372 | newLongRecordPointer = getLongRecordHelper().storeLongRecord(value, _transaction.isActive()); | 1365 | newLongRecordPointer = getLongRecordHelper().storeLongRecord(value, _transaction.isActive()); |
60 | 1373 | } | 1366 | } |
61 | 1374 | 1367 | ||
62 | 1375 | if (!_ignoreTransactions && ((options & StoreOptions.DONT_JOURNAL) == 0)) { | 1368 | if (!_ignoreTransactions && ((options & StoreOptions.DONT_JOURNAL) == 0)) { |
63 | @@ -1396,7 +1389,7 @@ | |||
64 | 1396 | if (!committed && newLongRecordPointerMVV != 0) { | 1389 | if (!committed && newLongRecordPointerMVV != 0) { |
65 | 1397 | _volume.getStructure().deallocateGarbageChain(newLongRecordPointerMVV, 0); | 1390 | _volume.getStructure().deallocateGarbageChain(newLongRecordPointerMVV, 0); |
66 | 1398 | newLongRecordPointerMVV = 0; | 1391 | newLongRecordPointerMVV = 0; |
68 | 1399 | _spareValue.changeLongRecordMode(false); | 1392 | spareValue.changeLongRecordMode(false); |
69 | 1400 | } | 1393 | } |
70 | 1401 | 1394 | ||
71 | 1402 | if (treeClaimRequired && !treeClaimAcquired) { | 1395 | if (treeClaimRequired && !treeClaimAcquired) { |
72 | @@ -1462,37 +1455,38 @@ | |||
73 | 1462 | if (keyExisted) { | 1455 | if (keyExisted) { |
74 | 1463 | oldLongRecordPointer = buffer.fetchLongRecordPointer(foundAt); | 1456 | oldLongRecordPointer = buffer.fetchLongRecordPointer(foundAt); |
75 | 1464 | } | 1457 | } |
87 | 1465 | if (doAnyFetch) { | 1458 | |
88 | 1466 | buffer.fetch(foundAt, _spareValue); | 1459 | if (doFetch || doMVCC) { |
89 | 1467 | /* | 1460 | buffer.fetch(foundAt, spareValue); |
90 | 1468 | * If we aren't in MVCC we have to un-long-ify as | 1461 | if (oldLongRecordPointer != 0) { |
91 | 1469 | * fetch was requested. Otherwise only do it if it | 1462 | if (isLongMVV(spareValue)) { |
81 | 1470 | * is a long MVV so as to not-needlessly create one. | ||
82 | 1471 | */ | ||
83 | 1472 | if (!doMVCC) { | ||
84 | 1473 | fetchFixupForLongRecords(_spareValue, Integer.MAX_VALUE); | ||
85 | 1474 | } else if (oldLongRecordPointer != 0) { | ||
86 | 1475 | if (isLongMVV(_spareValue)) { | ||
92 | 1476 | oldLongRecordPointerMVV = oldLongRecordPointer; | 1463 | oldLongRecordPointerMVV = oldLongRecordPointer; |
94 | 1477 | fetchFixupForLongRecords(_spareValue, Integer.MAX_VALUE); | 1464 | fetchFixupForLongRecords(spareValue, Integer.MAX_VALUE); |
95 | 1478 | } | 1465 | } |
102 | 1479 | /* | 1466 | } |
103 | 1480 | * If it was a long MVV we saved it into the | 1467 | /* |
104 | 1481 | * variable above. Otherwise it is a primordial | 1468 | * If it was a long MVV we saved it into the |
105 | 1482 | * value that we can't get rid of. | 1469 | * variable above. Otherwise it is a |
106 | 1483 | */ | 1470 | * primordial value that we can't get rid |
107 | 1484 | oldLongRecordPointer = 0; | 1471 | * of. |
108 | 1472 | */ | ||
109 | 1473 | oldLongRecordPointer = 0; | ||
110 | 1474 | |||
111 | 1475 | if (doFetch) { | ||
112 | 1476 | spareValue.copyTo(_spareValue); | ||
113 | 1477 | fetchFromValueInternal(_spareValue, Integer.MAX_VALUE, buffer); | ||
114 | 1485 | } | 1478 | } |
115 | 1486 | } | 1479 | } |
116 | 1480 | |||
117 | 1487 | if (doMVCC) { | 1481 | if (doMVCC) { |
119 | 1488 | valueToStore = _spareValue; | 1482 | valueToStore = spareValue; |
120 | 1489 | int valueSize = value.getEncodedSize(); | 1483 | int valueSize = value.getEncodedSize(); |
121 | 1490 | /* | 1484 | /* |
122 | 1491 | * If key didn't exist the value is truly | 1485 | * If key didn't exist the value is truly |
123 | 1492 | * non-existent and not just undefined/zero length | 1486 | * non-existent and not just undefined/zero length |
124 | 1493 | */ | 1487 | */ |
127 | 1494 | byte[] spareBytes = _spareValue.getEncodedBytes(); | 1488 | byte[] spareBytes = spareValue.getEncodedBytes(); |
128 | 1495 | int spareSize = keyExisted ? _spareValue.getEncodedSize() : -1; | 1489 | int spareSize = keyExisted ? spareValue.getEncodedSize() : -1; |
129 | 1496 | spareSize = MVV.prune(spareBytes, 0, spareSize, _persistit.getTransactionIndex(), false, | 1490 | spareSize = MVV.prune(spareBytes, 0, spareSize, _persistit.getTransactionIndex(), false, |
130 | 1497 | prunedVersions); | 1491 | prunedVersions); |
131 | 1498 | 1492 | ||
132 | @@ -1521,8 +1515,8 @@ | |||
133 | 1521 | MVV.visitAllVersions(_mvvVisitor, spareBytes, 0, spareSize); | 1515 | MVV.visitAllVersions(_mvvVisitor, spareBytes, 0, spareSize); |
134 | 1522 | 1516 | ||
135 | 1523 | int mvvSize = MVV.estimateRequiredLength(spareBytes, spareSize, valueSize); | 1517 | int mvvSize = MVV.estimateRequiredLength(spareBytes, spareSize, valueSize); |
138 | 1524 | _spareValue.ensureFit(mvvSize); | 1518 | spareValue.ensureFit(mvvSize); |
139 | 1525 | spareBytes = _spareValue.getEncodedBytes(); | 1519 | spareBytes = spareValue.getEncodedBytes(); |
140 | 1526 | 1520 | ||
141 | 1527 | long versionHandle = TransactionIndex.tss2vh(_transaction.getStartTimestamp(), tStep); | 1521 | long versionHandle = TransactionIndex.tss2vh(_transaction.getStartTimestamp(), tStep); |
142 | 1528 | int storedLength = MVV.storeVersion(spareBytes, 0, spareSize, spareBytes.length, | 1522 | int storedLength = MVV.storeVersion(spareBytes, 0, spareSize, spareBytes.length, |
143 | @@ -1530,11 +1524,10 @@ | |||
144 | 1530 | 1524 | ||
145 | 1531 | incrementMVVCount = (storedLength & MVV.STORE_EXISTED_MASK) == 0; | 1525 | incrementMVVCount = (storedLength & MVV.STORE_EXISTED_MASK) == 0; |
146 | 1532 | storedLength &= MVV.STORE_LENGTH_MASK; | 1526 | storedLength &= MVV.STORE_LENGTH_MASK; |
148 | 1533 | _spareValue.setEncodedSize(storedLength); | 1527 | spareValue.setEncodedSize(storedLength); |
149 | 1534 | 1528 | ||
153 | 1535 | if (_spareValue.getEncodedSize() > maxSimpleValueSize) { | 1529 | if (spareValue.getEncodedSize() > maxSimpleValueSize) { |
154 | 1536 | newLongRecordPointerMVV = getLongRecordHelper().storeLongRecord(_spareValue, | 1530 | newLongRecordPointerMVV = getLongRecordHelper().storeLongRecord(spareValue, _transaction.isActive()); |
152 | 1537 | _transaction.isActive()); | ||
155 | 1538 | } | 1531 | } |
156 | 1539 | } | 1532 | } |
157 | 1540 | } | 1533 | } |
158 | @@ -1673,7 +1666,7 @@ | |||
159 | 1673 | } | 1666 | } |
160 | 1674 | 1667 | ||
161 | 1675 | value.changeLongRecordMode(false); | 1668 | value.changeLongRecordMode(false); |
163 | 1676 | _spareValue.changeLongRecordMode(false); | 1669 | spareValue.changeLongRecordMode(false); |
164 | 1677 | if (!committed) { | 1670 | if (!committed) { |
165 | 1678 | // | 1671 | // |
166 | 1679 | // We failed to write the new LONG_RECORD. If there was | 1672 | // We failed to write the new LONG_RECORD. If there was |
167 | @@ -1698,7 +1691,7 @@ | |||
168 | 1698 | } | 1691 | } |
169 | 1699 | _volume.getStatistics().bumpStoreCounter(); | 1692 | _volume.getStatistics().bumpStoreCounter(); |
170 | 1700 | _tree.getStatistics().bumpStoreCounter(); | 1693 | _tree.getStatistics().bumpStoreCounter(); |
172 | 1701 | if (doAnyFetch) { | 1694 | if (doFetch || doMVCC) { |
173 | 1702 | _volume.getStatistics().bumpFetchCounter(); | 1695 | _volume.getStatistics().bumpFetchCounter(); |
174 | 1703 | _tree.getStatistics().bumpFetchCounter(); | 1696 | _tree.getStatistics().bumpFetchCounter(); |
175 | 1704 | } | 1697 | } |
176 | @@ -1761,7 +1754,6 @@ | |||
177 | 1761 | * The encoded insert location. | 1754 | * The encoded insert location. |
178 | 1762 | * @return <code>true</code> if it necessary to insert a key into the | 1755 | * @return <code>true</code> if it necessary to insert a key into the |
179 | 1763 | * ancestor index page. | 1756 | * ancestor index page. |
180 | 1764 | * @throws PMapException | ||
181 | 1765 | */ | 1757 | */ |
182 | 1766 | // TODO - Check insertIndexLevel timestamps | 1758 | // TODO - Check insertIndexLevel timestamps |
183 | 1767 | private boolean putLevel(LevelCache lc, Key key, ValueHelper valueWriter, Buffer buffer, int foundAt, | 1759 | private boolean putLevel(LevelCache lc, Key key, ValueHelper valueWriter, Buffer buffer, int foundAt, |
184 | @@ -2142,7 +2134,7 @@ | |||
185 | 2142 | index = _key.getEncodedSize(); | 2134 | index = _key.getEncodedSize(); |
186 | 2143 | 2135 | ||
187 | 2144 | if (matches) { | 2136 | if (matches) { |
189 | 2145 | matches = fetchInternal(buffer, outValue, foundAt, minimumBytes); | 2137 | matches = fetchFromBufferInternal(buffer, outValue, foundAt, minimumBytes); |
190 | 2146 | if (!matches && direction != EQ) { | 2138 | if (!matches && direction != EQ) { |
191 | 2147 | nudged = false; | 2139 | nudged = false; |
192 | 2148 | nudgeForMVCC = (direction == GTEQ || direction == LTEQ); | 2140 | nudgeForMVCC = (direction == GTEQ || direction == LTEQ); |
193 | @@ -2162,7 +2154,7 @@ | |||
194 | 2162 | if (matches) { | 2154 | if (matches) { |
195 | 2163 | index = _key.nextElementIndex(parentIndex); | 2155 | index = _key.nextElementIndex(parentIndex); |
196 | 2164 | if (index > 0) { | 2156 | if (index > 0) { |
198 | 2165 | boolean isVisibleMatch = fetchInternal(buffer, outValue, foundAt, minimumBytes); | 2157 | boolean isVisibleMatch = fetchFromBufferInternal(buffer, outValue, foundAt, minimumBytes); |
199 | 2166 | // | 2158 | // |
200 | 2167 | // In any case (matching sibling, child or | 2159 | // In any case (matching sibling, child or |
201 | 2168 | // niece/nephew) we need to ignore this | 2160 | // niece/nephew) we need to ignore this |
202 | @@ -2569,7 +2561,9 @@ | |||
203 | 2569 | _persistit.checkClosed(); | 2561 | _persistit.checkClosed(); |
204 | 2570 | _persistit.checkSuspended(); | 2562 | _persistit.checkSuspended(); |
205 | 2571 | _key.testValidForStoreAndFetch(_volume.getPageSize()); | 2563 | _key.testValidForStoreAndFetch(_volume.getPageSize()); |
207 | 2572 | storeInternal(_key, _value, 0, StoreOptions.FETCH | StoreOptions.WAIT); | 2564 | int options = StoreOptions.WAIT | StoreOptions.FETCH; |
208 | 2565 | options |= (!_ignoreTransactions && _transaction.isActive()) ? StoreOptions.MVCC : 0; | ||
209 | 2566 | storeInternal(_key, _value, 0, options); | ||
210 | 2573 | _spareValue.copyTo(_value); | 2567 | _spareValue.copyTo(_value); |
211 | 2574 | return this; | 2568 | return this; |
212 | 2575 | } | 2569 | } |
213 | @@ -2727,7 +2721,7 @@ | |||
214 | 2727 | if (minimumBytes < 0) { | 2721 | if (minimumBytes < 0) { |
215 | 2728 | minimumBytes = 0; | 2722 | minimumBytes = 0; |
216 | 2729 | } | 2723 | } |
218 | 2730 | fetchInternal(value, minimumBytes); | 2724 | searchAndFetchInternal(value, minimumBytes); |
219 | 2731 | return this; | 2725 | return this; |
220 | 2732 | } | 2726 | } |
221 | 2733 | 2727 | ||
222 | @@ -2748,9 +2742,29 @@ | |||
223 | 2748 | * As thrown from any internal method. | 2742 | * As thrown from any internal method. |
224 | 2749 | * @return <code>true</code> if the value was visible. | 2743 | * @return <code>true</code> if the value was visible. |
225 | 2750 | */ | 2744 | */ |
227 | 2751 | private boolean fetchInternal(Buffer buffer, Value value, int foundAt, int minimumBytes) throws PersistitException { | 2745 | private boolean fetchFromBufferInternal(Buffer buffer, Value value, int foundAt, int minimumBytes) throws PersistitException { |
228 | 2746 | buffer.fetch(foundAt, value); | ||
229 | 2747 | return fetchFromValueInternal(value, minimumBytes, buffer); | ||
230 | 2748 | } | ||
231 | 2749 | |||
232 | 2750 | /** | ||
233 | 2751 | * Helper for finalizing the value to return from a, potentially, MVV | ||
234 | 2752 | * contained in the given Value. | ||
235 | 2753 | * | ||
236 | 2754 | * @param value | ||
237 | 2755 | * Value to finalize. | ||
238 | 2756 | * @param minimumBytes | ||
239 | 2757 | * Minimum amount of LONG_RECORD to fetch. If <0, the | ||
240 | 2758 | * <code>value</code> will contain just the descriptor portion. | ||
241 | 2759 | * @param bufferForPruning | ||
242 | 2760 | * If not <code>null</code> and <code>Value</code> did contain | ||
243 | 2761 | * an MVV, call {@link Buffer#enqueuePruningAction(int)}. | ||
244 | 2762 | * @throws PersistitException | ||
245 | 2763 | * As thrown from any internal method. | ||
246 | 2764 | * @return <code>true</code> if the value was visible. | ||
247 | 2765 | */ | ||
248 | 2766 | private boolean fetchFromValueInternal(Value value, int minimumBytes, Buffer bufferForPruning) throws PersistitException { | ||
249 | 2752 | boolean visible = true; | 2767 | boolean visible = true; |
250 | 2753 | buffer.fetch(foundAt, value); | ||
251 | 2754 | /* | 2768 | /* |
252 | 2755 | * We must fetch the full LONG_RECORD, if needed, while buffer is | 2769 | * We must fetch the full LONG_RECORD, if needed, while buffer is |
253 | 2756 | * claimed from calling code so that it can't be de-allocated as we are | 2770 | * claimed from calling code so that it can't be de-allocated as we are |
254 | @@ -2763,7 +2777,9 @@ | |||
255 | 2763 | */ | 2777 | */ |
256 | 2764 | fetchFixupForLongRecords(value, Integer.MAX_VALUE); | 2778 | fetchFixupForLongRecords(value, Integer.MAX_VALUE); |
257 | 2765 | if (MVV.isArrayMVV(value.getEncodedBytes(), 0, value.getEncodedSize())) { | 2779 | if (MVV.isArrayMVV(value.getEncodedBytes(), 0, value.getEncodedSize())) { |
259 | 2766 | buffer.enqueuePruningAction(_tree.getHandle()); | 2780 | if (bufferForPruning != null) { |
260 | 2781 | bufferForPruning.enqueuePruningAction(_tree.getHandle()); | ||
261 | 2782 | } | ||
262 | 2767 | visible = mvccFetch(value, minimumBytes); | 2783 | visible = mvccFetch(value, minimumBytes); |
263 | 2768 | fetchFixupForLongRecords(value, minimumBytes); | 2784 | fetchFixupForLongRecords(value, minimumBytes); |
264 | 2769 | } | 2785 | } |
265 | @@ -2790,13 +2806,13 @@ | |||
266 | 2790 | * @throws PersistitException | 2806 | * @throws PersistitException |
267 | 2791 | * As thrown from {@link #search(Key, boolean)} | 2807 | * As thrown from {@link #search(Key, boolean)} |
268 | 2792 | */ | 2808 | */ |
270 | 2793 | private void fetchInternal(Value value, int minimumBytes) throws PersistitException { | 2809 | private void searchAndFetchInternal(Value value, int minimumBytes) throws PersistitException { |
271 | 2794 | Buffer buffer = null; | 2810 | Buffer buffer = null; |
272 | 2795 | try { | 2811 | try { |
273 | 2796 | int foundAt = search(_key, false); | 2812 | int foundAt = search(_key, false); |
274 | 2797 | LevelCache lc = _levelCache[0]; | 2813 | LevelCache lc = _levelCache[0]; |
275 | 2798 | buffer = lc._buffer; | 2814 | buffer = lc._buffer; |
277 | 2799 | fetchInternal(buffer, value, foundAt, minimumBytes); | 2815 | fetchFromBufferInternal(buffer, value, foundAt, minimumBytes); |
278 | 2800 | _volume.getStatistics().bumpFetchCounter(); | 2816 | _volume.getStatistics().bumpFetchCounter(); |
279 | 2801 | _tree.getStatistics().bumpFetchCounter(); | 2817 | _tree.getStatistics().bumpFetchCounter(); |
280 | 2802 | } finally { | 2818 | } finally { |
281 | @@ -3063,7 +3079,7 @@ | |||
282 | 3063 | 3079 | ||
283 | 3064 | _value.clear().putAntiValueMVV(); | 3080 | _value.clear().putAntiValueMVV(); |
284 | 3065 | final int storeOptions = StoreOptions.MVCC | StoreOptions.WAIT | StoreOptions.ONLY_IF_VISIBLE | 3081 | final int storeOptions = StoreOptions.MVCC | StoreOptions.WAIT | StoreOptions.ONLY_IF_VISIBLE |
286 | 3066 | | StoreOptions.DONT_JOURNAL; | 3082 | | StoreOptions.DONT_JOURNAL | (fetchFirst ? StoreOptions.FETCH : 0); |
287 | 3067 | 3083 | ||
288 | 3068 | boolean anyRemoved = false; | 3084 | boolean anyRemoved = false; |
289 | 3069 | boolean keyIsLessThan = true; | 3085 | boolean keyIsLessThan = true; |
290 | @@ -3755,8 +3771,7 @@ | |||
291 | 3755 | /** | 3771 | /** |
292 | 3756 | * Called by Transaction to set up a context for committing updates. | 3772 | * Called by Transaction to set up a context for committing updates. |
293 | 3757 | * | 3773 | * |
296 | 3758 | * @param volume | 3774 | * @param tree |
295 | 3759 | * @param _treeName | ||
297 | 3760 | */ | 3775 | */ |
298 | 3761 | void setTree(Tree tree) throws PersistitException { | 3776 | void setTree(Tree tree) throws PersistitException { |
299 | 3762 | _persistit.checkClosed(); | 3777 | _persistit.checkClosed(); |
300 | @@ -3944,7 +3959,7 @@ | |||
301 | 3944 | boolean savedIgnore = _ignoreMVCCFetch; | 3959 | boolean savedIgnore = _ignoreMVCCFetch; |
302 | 3945 | try { | 3960 | try { |
303 | 3946 | _ignoreMVCCFetch = true; | 3961 | _ignoreMVCCFetch = true; |
305 | 3947 | fetchInternal(_spareValue, -1); | 3962 | searchAndFetchInternal(_spareValue, -1); |
306 | 3948 | final boolean wasLong = isLongRecord(_spareValue); | 3963 | final boolean wasLong = isLongRecord(_spareValue); |
307 | 3949 | _spareValue.clear(); | 3964 | _spareValue.clear(); |
308 | 3950 | return wasLong; | 3965 | return wasLong; |
309 | @@ -3966,7 +3981,7 @@ | |||
310 | 3966 | boolean savedIgnore = _ignoreMVCCFetch; | 3981 | boolean savedIgnore = _ignoreMVCCFetch; |
311 | 3967 | try { | 3982 | try { |
312 | 3968 | _ignoreMVCCFetch = true; | 3983 | _ignoreMVCCFetch = true; |
314 | 3969 | fetchInternal(_spareValue, -1); | 3984 | searchAndFetchInternal(_spareValue, -1); |
315 | 3970 | final boolean wasLong = isLongMVV(_spareValue); | 3985 | final boolean wasLong = isLongMVV(_spareValue); |
316 | 3971 | _spareValue.clear(); | 3986 | _spareValue.clear(); |
317 | 3972 | return wasLong; | 3987 | return wasLong; |
318 | 3973 | 3988 | ||
319 | === added file 'src/test/java/com/persistit/Bug912514Test.java' | |||
320 | --- src/test/java/com/persistit/Bug912514Test.java 1970-01-01 00:00:00 +0000 | |||
321 | +++ src/test/java/com/persistit/Bug912514Test.java 2012-05-29 19:52:29 +0000 | |||
322 | @@ -0,0 +1,114 @@ | |||
323 | 1 | /** | ||
324 | 2 | * Copyright © 2012 Akiban Technologies, Inc. All rights reserved. | ||
325 | 3 | * | ||
326 | 4 | * This program is free software: you can redistribute it and/or modify | ||
327 | 5 | * it under the terms of the GNU Affero General Public License as | ||
328 | 6 | * published by the Free Software Foundation, version 3 (only) of the | ||
329 | 7 | * License. | ||
330 | 8 | * | ||
331 | 9 | * This program is distributed in the hope that it will be useful, | ||
332 | 10 | * but WITHOUT ANY WARRANTY; without even the implied warranty of | ||
333 | 11 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the | ||
334 | 12 | * GNU Affero General Public License for more details. | ||
335 | 13 | * | ||
336 | 14 | * You should have received a copy of the GNU Affero General Public License | ||
337 | 15 | * along with this program. If not, see <http://www.gnu.org/licenses/>. | ||
338 | 16 | * | ||
339 | 17 | * This program may also be available under different license terms. For more | ||
340 | 18 | * information, see www.akiban.com or contact licensing@akiban.com. | ||
341 | 19 | */ | ||
342 | 20 | |||
343 | 21 | package com.persistit; | ||
344 | 22 | |||
345 | 23 | import static org.junit.Assert.*; | ||
346 | 24 | |||
347 | 25 | import org.junit.Test; | ||
348 | 26 | |||
349 | 27 | import com.persistit.unit.PersistitUnitTestCase; | ||
350 | 28 | |||
351 | 29 | /** | ||
352 | 30 | * https://bugs.launchpad.net/akiban-persistit/+bug/912514 | ||
353 | 31 | * | ||
354 | 32 | * PersistitMapStress1 fails intermittently with messages like this: | ||
355 | 33 | * | ||
356 | 34 | * Finished unit=#1 PersistitMapStress test=PersistitMapStress1 main at ts=8036 | ||
357 | 35 | * - elapsed=5738 - FAILED: value not expected to be null 8036 Failed test | ||
358 | 36 | * unit=#1 PersistitMapStress test=PersistitMapStress1 main value not expected | ||
359 | 37 | * to be null | ||
360 | 38 | * | ||
361 | 39 | * I didn't track down the responsible code, but I'm pretty sure the contents of | ||
362 | 40 | * _spareValue have been mangled. | ||
363 | 41 | * | ||
364 | 42 | * Since akiban-server does not use this method the bug is no more than medium | ||
365 | 43 | * priority. But it does need to be fixed before akiban-persistit is released as | ||
366 | 44 | * a standalone library. | ||
367 | 45 | */ | ||
368 | 46 | |||
369 | 47 | public class Bug912514Test extends PersistitUnitTestCase { | ||
370 | 48 | |||
371 | 49 | private final static String ROLLBACK = "rollback"; | ||
372 | 50 | |||
373 | 51 | private void fetchAndStoreAndRemoveHelper(final boolean inTxn, final String... sequence) throws Exception { | ||
374 | 52 | final Transaction txn = _persistit.getTransaction(); | ||
375 | 53 | final Exchange exchange = _persistit.getExchange("persistit", "Bug912514Test", true); | ||
376 | 54 | String previous = null; | ||
377 | 55 | for (String string : sequence) { | ||
378 | 56 | |||
379 | 57 | if (inTxn) { | ||
380 | 58 | txn.begin(); | ||
381 | 59 | } | ||
382 | 60 | |||
383 | 61 | if (string == null) { | ||
384 | 62 | exchange.to(1).fetchAndRemove(); | ||
385 | 63 | } else { | ||
386 | 64 | exchange.getValue().put(string); | ||
387 | 65 | exchange.to(1).fetchAndStore(); | ||
388 | 66 | } | ||
389 | 67 | compare(previous, exchange.getValue()); | ||
390 | 68 | |||
391 | 69 | if (inTxn) { | ||
392 | 70 | if (string.startsWith(ROLLBACK)) { | ||
393 | 71 | txn.rollback(); | ||
394 | 72 | } else { | ||
395 | 73 | txn.commit(); | ||
396 | 74 | } | ||
397 | 75 | txn.end(); | ||
398 | 76 | compare(previous, exchange.getValue()); | ||
399 | 77 | } | ||
400 | 78 | |||
401 | 79 | if (!inTxn || !string.startsWith(ROLLBACK)) { | ||
402 | 80 | previous = string; | ||
403 | 81 | } | ||
404 | 82 | exchange.fetch(); | ||
405 | 83 | compare(previous, exchange.getValue()); | ||
406 | 84 | |||
407 | 85 | |||
408 | 86 | } | ||
409 | 87 | } | ||
410 | 88 | |||
411 | 89 | private void compare(final String string, final Value value) { | ||
412 | 90 | if (string == null) { | ||
413 | 91 | assertTrue("Value should be undefined", !value.isDefined()); | ||
414 | 92 | } else { | ||
415 | 93 | assertEquals("Value should match", string, value.getString()); | ||
416 | 94 | } | ||
417 | 95 | } | ||
418 | 96 | |||
419 | 97 | @Test | ||
420 | 98 | public void fetchAndStoreTxn() throws Exception { | ||
421 | 99 | fetchAndStoreAndRemoveHelper(true, RED_FOX, createString(100), createString(1000), createString(10000), RED_FOX); | ||
422 | 100 | } | ||
423 | 101 | |||
424 | 102 | @Test | ||
425 | 103 | public void fetchAndRemoveNonTxn() throws Exception { | ||
426 | 104 | fetchAndStoreAndRemoveHelper(false, RED_FOX, null, null, createString(100), null, createString(1000), null, | ||
427 | 105 | createString(10000), null, null, RED_FOX, null); | ||
428 | 106 | } | ||
429 | 107 | |||
430 | 108 | @Test | ||
431 | 109 | public void fetchAndStoreTxnWithRollbacks() throws Exception { | ||
432 | 110 | fetchAndStoreAndRemoveHelper(true, RED_FOX, createString(100), ROLLBACK, createString(1000), ROLLBACK | ||
433 | 111 | + createString(10000), createString(10000), RED_FOX); | ||
434 | 112 | } | ||
435 | 113 | |||
436 | 114 | } |
Refactorings look good. Thank you.