Merge lp:~pbeaman/akiban-persistit/fix_1017957_stress_tests_corruption into lp:akiban-persistit

Proposed by Peter Beaman
Status: Merged
Approved by: Peter Beaman
Approved revision: 332
Merged at revision: 329
Proposed branch: lp:~pbeaman/akiban-persistit/fix_1017957_stress_tests_corruption
Merge into: lp:akiban-persistit
Diff against target: 1021 lines (+436/-148)
12 files modified
src/main/java/com/persistit/Buffer.java (+42/-47)
src/main/java/com/persistit/BufferPool.java (+1/-0)
src/main/java/com/persistit/CLI.java (+42/-14)
src/main/java/com/persistit/Exchange.java (+41/-75)
src/main/java/com/persistit/IntegrityCheck.java (+16/-9)
src/main/java/com/persistit/VolumeStorageV2.java (+1/-0)
src/main/java/com/persistit/exception/RetryException.java (+0/-3)
src/main/java/com/persistit/util/Debug.java (+26/-0)
src/main/java/com/persistit/util/SequencerConstants.java (+1/-0)
src/main/java/com/persistit/util/Util.java (+8/-0)
src/test/java/com/persistit/Bug1017957Test.java (+235/-0)
src/test/java/com/persistit/IntegrityCheckTest.java (+23/-0)
To merge this branch: bzr merge lp:~pbeaman/akiban-persistit/fix_1017957_stress_tests_corruption
Reviewer Review Type Date Requested Status
Nathan Williams Approve
Review via email: mp+112647@code.launchpad.net

Description of the change

Fix bug 1017957 - B-Tree corruption occasionally detected in stress tests.

This branch contains a new test, Bug1018526Test, that demonstrates the bug mechanism we thing is responsible for the failures.

Also included: incremental corrections in IntegrityCheck discovered when it failed to report the corrupt garbage chain correctly.

To post a comment you must log in.
330. By Peter Beaman

Version fixes corruption bug

331. By Peter Beaman

Cleanup

332. By Peter Beaman

Cleanup

333. By Peter Beaman

Cleanup

Revision history for this message
Peter Beaman (pbeaman) wrote :

Turns out there were two problems in the Exchange class that contributed to this bug, making diagnosis somewhat difficult.

This branch fixes both:

(a) There is no longer a concept of "deferred reindexing" in raw_removeKeyRangeInternal. That was a relic of the fix-length write ahead journal of the past. One of the bug mechanisms used that code path to reinsert a page pointer to a page that had changed due to releasing all locks.

(b) there was a code path that failed to bump the tree generation counter upon completing a structure delete.

To support diagnosing this bug, I added better formatting and a new, useful CLI command that walks a pointer chain and displays selected information about a page. Some of these changes are located in Buffer#toStringDetail which is now simpler and more useful.

Also, while diagnosing this bug I came across Exceptions while reporting garbage chain structure errors in IntegrityCheck. Fixed that problem and wrote a unit test to verify it.

And of course the Eclipse random line-length formatter made its usual contribution...

Revision history for this message
Nathan Williams (nwilliams) wrote :

The fix looks reasonable and seems to match the explanation.

I only skimmed the Buffer toString or IntegrityCheck changes but they look fine.

Please change the first sentence of the merge prop to something descriptive (it is used for the commit message) and feel free to Approve.

review: Approve

Preview Diff

[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
=== modified file 'src/main/java/com/persistit/Buffer.java'
--- src/main/java/com/persistit/Buffer.java 2012-06-25 15:41:14 +0000
+++ src/main/java/com/persistit/Buffer.java 2012-06-29 21:06:19 +0000
@@ -28,7 +28,6 @@
2828
29import java.nio.ByteBuffer;29import java.nio.ByteBuffer;
30import java.util.ArrayList;30import java.util.ArrayList;
31import java.util.BitSet;
32import java.util.List;31import java.util.List;
33import java.util.Set;32import java.util.Set;
3433
@@ -269,7 +268,7 @@
269 public final static int MAX_KEY_RATIO = 16;268 public final static int MAX_KEY_RATIO = 16;
270269
271 private final static int BINARY_SEARCH_THRESHOLD = 6;270 private final static int BINARY_SEARCH_THRESHOLD = 6;
272 271
273 abstract static class VerifyVisitor {272 abstract static class VerifyVisitor {
274273
275 protected void visitPage(long timestamp, Volume volume, long page, int type, int bufferSize, int keyBlockStart,274 protected void visitPage(long timestamp, Volume volume, long page, int type, int bufferSize, int keyBlockStart,
@@ -330,7 +329,7 @@
330 * _byteBuffer.329 * _byteBuffer.
331 */330 */
332 private byte[] _bytes;331 private byte[] _bytes;
333 332
334 /**333 /**
335 * FastIndex structure used for rapid page searching334 * FastIndex structure used for rapid page searching
336 */335 */
@@ -2318,6 +2317,7 @@
2318 assertVerify();2317 assertVerify();
2319 rightSibling.assertVerify();2318 rightSibling.assertVerify();
2320 }2319 }
2320
2321 return whereInserted;2321 return whereInserted;
2322 }2322 }
23232323
@@ -2594,6 +2594,7 @@
2594 buffer.assertVerify();2594 buffer.assertVerify();
2595 }2595 }
2596 }2596 }
2597
2597 return result;2598 return result;
2598 }2599 }
25992600
@@ -2815,7 +2816,7 @@
2815 }2816 }
28162817
2817 synchronized void invalidateFastIndex() {2818 synchronized void invalidateFastIndex() {
2818 _fastIndex.invalidate();2819 _fastIndex.invalidate();
2819 }2820 }
28202821
2821 synchronized FastIndex getFastIndex() {2822 synchronized FastIndex getFastIndex() {
@@ -3775,7 +3776,7 @@
37753776
3776 public String toString() {3777 public String toString() {
3777 if (_toStringDebug) {3778 if (_toStringDebug) {
3778 return toStringDetail(-1);3779 return toStringDetail();
3779 }3780 }
3780 return String.format("Page %,d in volume %s at index %,d timestamp=%,d status=%s type=%s", _page, _vol,3781 return String.format("Page %,d in volume %s at index %,d timestamp=%,d status=%s type=%s", _page, _vol,
3781 _poolIndex, _timestamp, getStatusDisplayString(), getPageTypeName());3782 _poolIndex, _timestamp, getStatusDisplayString(), getPageTypeName());
@@ -3785,7 +3786,7 @@
3785 * @return a human-readable inventory of the contents of this buffer3786 * @return a human-readable inventory of the contents of this buffer
3786 */3787 */
3787 public String toStringDetail() {3788 public String toStringDetail() {
3788 return toStringDetail(-1);3789 return toStringDetail(-1, 42, 42, 0, true);
3789 }3790 }
37903791
3791 /**3792 /**
@@ -3795,7 +3796,8 @@
3795 * the one that points to findPointer. This provides a way to3796 * the one that points to findPointer. This provides a way to
3796 * quickly find pointer paths in pages.3797 * quickly find pointer paths in pages.
3797 */3798 */
3798 String toStringDetail(final long findPointer) {3799 String toStringDetail(final long findPointer, final int maxKeyDisplayLength, final int maxValueDisplayLength,
3800 final int contextLines, final boolean all) {
3799 final StringBuilder sb = new StringBuilder(String.format(3801 final StringBuilder sb = new StringBuilder(String.format(
3800 "Page %,d in volume %s at index @%,d status %s type %s", _page, _vol, _poolIndex,3802 "Page %,d in volume %s at index @%,d status %s type %s", _page, _vol, _poolIndex,
3801 getStatusDisplayString(), getPageTypeName()));3803 getStatusDisplayString(), getPageTypeName()));
@@ -3805,52 +3807,53 @@
3805 sb.append(String.format("\n type=%,d alloc=%,d slack=%,d " + "keyBlockStart=%,d keyBlockEnd=%,d "3807 sb.append(String.format("\n type=%,d alloc=%,d slack=%,d " + "keyBlockStart=%,d keyBlockEnd=%,d "
3806 + "timestamp=%,d generation=%,d right=%,d hash=%,d", _type, _alloc, _slack, KEY_BLOCK_START,3808 + "timestamp=%,d generation=%,d right=%,d hash=%,d", _type, _alloc, _slack, KEY_BLOCK_START,
3807 _keyBlockEnd, getTimestamp(), getGeneration(), getRightSibling(), _pool.hashIndex(_vol, _page)));3809 _keyBlockEnd, getTimestamp(), getGeneration(), getRightSibling(), _pool.hashIndex(_vol, _page)));
3808
3809 try {3810 try {
3810 final Key key = new Key(_persistit);3811 final Key key = new Key(_persistit);
3811 final Value value = new Value(_persistit);3812 final Value value = new Value(_persistit);
3812 final RecordInfo[] records = getRecords();3813 final RecordInfo[] records = getRecords();
3813 BitSet bits = new BitSet();3814 int foundPointerRecord = -1;
3814 if (isIndexPage() && findPointer >= 0) {3815 if (isIndexPage() && findPointer >= 0) {
3815 for (int index = 0; index < records.length; index++) {3816 for (int index = 0; index < records.length; index++) {
3816 if (records[index].getPointerValue() == findPointer) {3817 if (records[index].getPointerValue() == findPointer) {
3817 bits.set(index);3818 foundPointerRecord = index;
3818 }3819 }
3819 }3820 }
3820 }3821 }
3821 boolean elisionMarked = false;3822 boolean elision = false;
3822 for (int index = 0; index < records.length; index++) {3823 for (int index = 0; index < records.length; index++) {
3823 RecordInfo r = records[index];3824 RecordInfo r = records[index];
3824 r.getKeyState().copyTo(key);3825 r.getKeyState().copyTo(key);
3825 if (isDataPage()) {3826 String mark = " ";
3826 r.getValueState().copyTo(value);3827 boolean selected = all | index < contextLines || index >= records.length - contextLines;
3827 sb.append(String.format("\n %5d: db=%3d ebc=%3d tb=%,5d [%,d]%s=[%,d]%s", r.getKbOffset(), r3828 if (foundPointerRecord >= 0) {
3828 .getDb(), r.getEbc(), r.getTbOffset(), r.getKLength(), key, r.getValueState()3829 selected |= (index >= foundPointerRecord - contextLines)
3829 .getEncodedBytes().length, abridge(value)));3830 && (index <= foundPointerRecord + contextLines);
3831 if (index == foundPointerRecord) {
3832 mark = "*";
3833 }
3834 }
3835
3836 if (selected) {
3837 if (elision) {
3838 sb.append(String.format("\n ..."));
3839 elision = false;
3840 }
3841 if (isDataPage()) {
3842 r.getValueState().copyTo(value);
3843 sb.append(String.format("\n%s %5d: db=%3d ebc=%3d tb=%,5d [%,d]%s=[%,d]%s", mark, r
3844 .getKbOffset(), r.getDb(), r.getEbc(), r.getTbOffset(), r.getKLength(), Util
3845 .abridge(key.toString(), maxKeyDisplayLength),
3846 r.getValueState().getEncodedBytes().length, Util.abridge(value.toString(),
3847 maxValueDisplayLength)));
3848 } else {
3849 sb.append(String.format("\n%s %5d: db=%3d ebc=%3d tb=%,5d [%,d]%s->%,d", mark, r
3850 .getKbOffset(), r.getDb(), r.getEbc(), r.getTbOffset(), r.getKLength(), Util
3851 .abridge(key.toString(), maxKeyDisplayLength), r.getPointerValue()));
3852 }
3830 } else {3853 } else {
3831 boolean selected = true;3854 elision = true;
3832 if (findPointer >= 0) {
3833 if (index > 2 && index < records.length - 2) {
3834 boolean bit = false;
3835 for (int p = index - 2; p < index + 3; p++) {
3836 if (bits.get(p)) {
3837 bit = true;
3838 }
3839 }
3840 selected &= bit;
3841 }
3842 }
3843 if (selected) {
3844 sb.append(String.format("\n %5d: db=%3d ebc=%3d tb=%,5d [%,d]%s->%,d", r.getKbOffset(), r
3845 .getDb(), r.getEbc(), r.getTbOffset(), r.getKLength(), key, r.getPointerValue()));
3846 elisionMarked = false;
3847 } else {
3848 if (!elisionMarked) {
3849 sb.append(String.format("\n ..."));
3850 elisionMarked = true;
3851 }
3852 }
3853 }3855 }
3856
3854 }3857 }
3855 } catch (Exception e) {3858 } catch (Exception e) {
3856 sb.append(" - " + e);3859 sb.append(" - " + e);
@@ -3875,15 +3878,6 @@
3875 return sb.toString();3878 return sb.toString();
3876 }3879 }
38773880
3878 private String abridge(final Value value) {
3879 String s = value.toString();
3880 if (s.length() > 120) {
3881 return s.substring(0, 120) + "...";
3882 } else {
3883 return s;
3884 }
3885 }
3886
3887 String foundAtString(int p) {3881 String foundAtString(int p) {
3888 StringBuilder sb = new StringBuilder("<");3882 StringBuilder sb = new StringBuilder("<");
3889 sb.append(p & P_MASK);3883 sb.append(p & P_MASK);
@@ -4316,4 +4310,5 @@
4316 ++_mvvCount;4310 ++_mvvCount;
4317 }4311 }
4318 }4312 }
4313
4319}4314}
43204315
=== modified file 'src/main/java/com/persistit/BufferPool.java'
--- src/main/java/com/persistit/BufferPool.java 2012-06-14 17:55:08 +0000
+++ src/main/java/com/persistit/BufferPool.java 2012-06-29 21:06:19 +0000
@@ -1054,6 +1054,7 @@
1054 }1054 }
10551055
1056 int selectDirtyBuffers(final int[] priorities, final BufferHolder[] holders) throws PersistitException {1056 int selectDirtyBuffers(final int[] priorities, final BufferHolder[] holders) throws PersistitException {
1057 Debug.suspend();
1057 int count = 0;1058 int count = 0;
1058 final int clock = _clock.get();1059 final int clock = _clock.get();
10591060
10601061
=== modified file 'src/main/java/com/persistit/CLI.java'
--- src/main/java/com/persistit/CLI.java 2012-06-14 20:15:48 +0000
+++ src/main/java/com/persistit/CLI.java 2012-06-29 21:06:19 +0000
@@ -1021,6 +1021,9 @@
1021 final @Arg("pageSize|int:16384:1024:16384|Buffer pool index") int pageSize,1021 final @Arg("pageSize|int:16384:1024:16384|Buffer pool index") int pageSize,
1022 final @Arg("level|int:0:0:30|Tree level") int level, final @Arg("key|string|Key") String keyString,1022 final @Arg("level|int:0:0:30|Tree level") int level, final @Arg("key|string|Key") String keyString,
1023 final @Arg("find|long:-1:0:99999999999999999|Optional page pointer to find") long findPointer,1023 final @Arg("find|long:-1:0:99999999999999999|Optional page pointer to find") long findPointer,
1024 final @Arg("maxkey|int:42:4:10000|Maximum displayed key length") int maxkey,
1025 final @Arg("maxvalue|int:42:4:100000|Maximum displayed value length") int maxvalue,
1026 final @Arg("context|int:3:0:100000|Context lines") int context,
1024 final @Arg("_flag|a|All lines") boolean allLines, final @Arg("_flag|s|Summary only") boolean summary)1027 final @Arg("_flag|a|All lines") boolean allLines, final @Arg("_flag|s|Summary only") boolean summary)
1025 throws Exception {1028 throws Exception {
10261029
@@ -1084,21 +1087,8 @@
1084 if (summary) {1087 if (summary) {
1085 postMessage(buffer.toString(), LOG_NORMAL);1088 postMessage(buffer.toString(), LOG_NORMAL);
1086 return;1089 return;
1087 }
1088 String detail = buffer.toStringDetail(findPointer);
1089 if (allLines) {
1090 postMessage(detail, LOG_NORMAL);
1091 return;
1092 } else {1090 } else {
1093 int p = -1;1091 postMessage(buffer.toStringDetail(findPointer, maxkey, maxvalue, context, allLines), LOG_NORMAL);
1094 for (int i = 0; i < 20; i++) {
1095 p = detail.indexOf('\n', p + 1);
1096 if (p == -1) {
1097 p = detail.length();
1098 break;
1099 }
1100 }
1101 postMessage(detail.substring(0, p), LOG_NORMAL);
1102 return;1092 return;
1103 }1093 }
1104 }1094 }
@@ -1108,6 +1098,44 @@
1108 }1098 }
1109 };1099 };
1110 }1100 }
1101
1102 @Cmd("pviewchain")
1103 Task pviewchain(final @Arg("page|long:0:0:99999999999999999|Starting page address") long pageAddress,
1104 final @Arg("find|long:-1:0:99999999999999999|Optional page pointer to find") long findPointer,
1105 final @Arg("count|int:32:1:1000000|Maximum number of pages to display") long maxcount,
1106 final @Arg("maxkey|int:42:4:10000|Maximum displayed key length") int maxkey,
1107 final @Arg("maxvalue|int:42:4:100000|Maximum displayed value length") int maxvalue,
1108 final @Arg("context|int:3:0:100000|Context lines") int context,
1109 final @Arg("_flag|a|All lines") boolean allLines, final @Arg("_flag|s|Summary only") boolean summary) {
1110
1111 return new Task() {
1112
1113 @Override
1114 protected void runTask() throws Exception {
1115 long currentPage = pageAddress;
1116 int count = 0;
1117 while (currentPage > 0 && count++ < maxcount) {
1118 if (_currentVolume == null) {
1119 postMessage("Select a volume", LOG_NORMAL);
1120 return;
1121 }
1122 final Buffer buffer = _currentVolume.getPool().getBufferCopy(_currentVolume, currentPage);
1123 if (summary) {
1124 postMessage(buffer.toString(), LOG_NORMAL);
1125 } else {
1126 postMessage(buffer.toStringDetail(findPointer, maxkey, maxvalue, context, allLines), LOG_NORMAL);
1127 }
1128 currentPage = buffer.getRightSibling();
1129 }
1130 }
1131
1132 @Override
1133 public String getStatus() {
1134 return "";
1135 }
1136
1137 };
1138 }
11111139
1112 @Cmd("jquery")1140 @Cmd("jquery")
1113 Task jquery(final @Arg("page|long:-1|Page address for PageNode to look up") long pageAddress,1141 Task jquery(final @Arg("page|long:-1|Page address for PageNode to look up") long pageAddress,
11141142
=== modified file 'src/main/java/com/persistit/Exchange.java'
--- src/main/java/com/persistit/Exchange.java 2012-06-18 16:35:52 +0000
+++ src/main/java/com/persistit/Exchange.java 2012-06-29 21:06:19 +0000
@@ -38,7 +38,7 @@
38import static com.persistit.Key.LTEQ;38import static com.persistit.Key.LTEQ;
39import static com.persistit.Key.RIGHT_GUARD_KEY;39import static com.persistit.Key.RIGHT_GUARD_KEY;
40import static com.persistit.Key.maxStorableKeySize;40import static com.persistit.Key.maxStorableKeySize;
41import static com.persistit.util.SequencerConstants.WRITE_WRITE_STORE_A;41import static com.persistit.util.SequencerConstants.*;
42import static com.persistit.util.ThreadSequencer.sequence;42import static com.persistit.util.ThreadSequencer.sequence;
4343
44import java.util.ArrayList;44import java.util.ArrayList;
@@ -526,8 +526,7 @@
526 int _flags;526 int _flags;
527 long _deallocLeftPage;527 long _deallocLeftPage;
528 long _deallocRightPage;528 long _deallocRightPage;
529 long _deferredReindexPage;529
530 long _deferredReindexChangeCount;
531530
532 private LevelCache(int level) {531 private LevelCache(int level) {
533 _level = level;532 _level = level;
@@ -1330,7 +1329,8 @@
13301329
1331 final int maxSimpleValueSize = maxValueSize(key.getEncodedSize());1330 final int maxSimpleValueSize = maxValueSize(key.getEncodedSize());
1332 final Value spareValue = _persistit.getThreadLocalValue();1331 final Value spareValue = _persistit.getThreadLocalValue();
1333 assert !(doMVCC & value == spareValue || doFetch && value == _spareValue): "storeInternal may be use the supplied Value: " + value;1332 assert !(doMVCC & value == spareValue || doFetch && value == _spareValue) : "storeInternal may be use the supplied Value: "
1333 + value;
13341334
1335 //1335 //
1336 // First insert the record in the data page1336 // First insert the record in the data page
@@ -1356,7 +1356,7 @@
1356 // This method may delay significantly for I/O and must1356 // This method may delay significantly for I/O and must
1357 // be called when there are no other claimed resources.1357 // be called when there are no other claimed resources.
1358 //1358 //
1359 newLongRecordPointer = getLongRecordHelper().storeLongRecord(value, _transaction.isActive());1359 newLongRecordPointer = getLongRecordHelper().storeLongRecord(value, _transaction.isActive());
1360 }1360 }
13611361
1362 if (!_ignoreTransactions && ((options & StoreOptions.DONT_JOURNAL) == 0)) {1362 if (!_ignoreTransactions && ((options & StoreOptions.DONT_JOURNAL) == 0)) {
@@ -1408,7 +1408,7 @@
1408 if (!treeClaimAcquired || !_treeHolder.upgradeClaim()) {1408 if (!treeClaimAcquired || !_treeHolder.upgradeClaim()) {
1409 treeClaimRequired = true;1409 treeClaimRequired = true;
1410 treeWriterClaimRequired = true;1410 treeWriterClaimRequired = true;
1411 throw new RetryException();1411 throw RetryException.SINGLE;
1412 }1412 }
14131413
1414 Debug.$assert0.t(valueToStore.getPointerValue() > 0);1414 Debug.$assert0.t(valueToStore.getPointerValue() > 0);
@@ -1460,9 +1460,8 @@
1460 }1460 }
1461 /*1461 /*
1462 * If it was a long MVV we saved it into the1462 * If it was a long MVV we saved it into the
1463 * variable above. Otherwise it is a1463 * variable above. Otherwise it is a primordial
1464 * primordial value that we can't get rid1464 * value that we can't get rid of.
1465 * of.
1466 */1465 */
1467 oldLongRecordPointer = 0;1466 oldLongRecordPointer = 0;
14681467
@@ -1521,7 +1520,8 @@
1521 spareValue.setEncodedSize(storedLength);1520 spareValue.setEncodedSize(storedLength);
15221521
1523 if (spareValue.getEncodedSize() > maxSimpleValueSize) {1522 if (spareValue.getEncodedSize() > maxSimpleValueSize) {
1524 newLongRecordPointerMVV = getLongRecordHelper().storeLongRecord(spareValue, _transaction.isActive());1523 newLongRecordPointerMVV = getLongRecordHelper().storeLongRecord(spareValue,
1524 _transaction.isActive());
1525 }1525 }
1526 }1526 }
1527 }1527 }
@@ -1797,13 +1797,15 @@
1797 // level cache for this level will become1797 // level cache for this level will become
1798 // (appropriately) invalid.1798 // (appropriately) invalid.
1799 //1799 //
1800
1801
1800 int at = buffer.split(rightSibling, key, valueWriter, foundAt, _spareKey1, sequence, _splitPolicy);1802 int at = buffer.split(rightSibling, key, valueWriter, foundAt, _spareKey1, sequence, _splitPolicy);
1801 if (at < 0) {1803 if (at < 0) {
1802 lc.updateInsert(rightSibling, key, -at);1804 lc.updateInsert(rightSibling, key, -at);
1803 } else {1805 } else {
1804 lc.updateInsert(buffer, key, at);1806 lc.updateInsert(buffer, key, at);
1805 }1807 }
18061808
1807 long oldRightSibling = buffer.getRightSibling();1809 long oldRightSibling = buffer.getRightSibling();
1808 long newRightSibling = rightSibling.getPageAddress();1810 long newRightSibling = rightSibling.getPageAddress();
18091811
@@ -2148,7 +2150,8 @@
2148 if (matches) {2150 if (matches) {
2149 index = _key.nextElementIndex(parentIndex);2151 index = _key.nextElementIndex(parentIndex);
2150 if (index > 0) {2152 if (index > 0) {
2151 boolean isVisibleMatch = fetchFromBufferInternal(buffer, outValue, foundAt, minimumBytes);2153 boolean isVisibleMatch = fetchFromBufferInternal(buffer, outValue, foundAt,
2154 minimumBytes);
2152 //2155 //
2153 // In any case (matching sibling, child or2156 // In any case (matching sibling, child or
2154 // niece/nephew) we need to ignore this2157 // niece/nephew) we need to ignore this
@@ -2736,7 +2739,8 @@
2736 * As thrown from any internal method.2739 * As thrown from any internal method.
2737 * @return <code>true</code> if the value was visible.2740 * @return <code>true</code> if the value was visible.
2738 */2741 */
2739 private boolean fetchFromBufferInternal(Buffer buffer, Value value, int foundAt, int minimumBytes) throws PersistitException {2742 private boolean fetchFromBufferInternal(Buffer buffer, Value value, int foundAt, int minimumBytes)
2743 throws PersistitException {
2740 buffer.fetch(foundAt, value);2744 buffer.fetch(foundAt, value);
2741 return fetchFromValueInternal(value, minimumBytes, buffer);2745 return fetchFromValueInternal(value, minimumBytes, buffer);
2742 }2746 }
@@ -2744,20 +2748,21 @@
2744 /**2748 /**
2745 * Helper for finalizing the value to return from a, potentially, MVV2749 * Helper for finalizing the value to return from a, potentially, MVV
2746 * contained in the given Value.2750 * contained in the given Value.
2747 *2751 *
2748 * @param value2752 * @param value
2749 * Value to finalize.2753 * Value to finalize.
2750 * @param minimumBytes2754 * @param minimumBytes
2751 * Minimum amount of LONG_RECORD to fetch. If &lt;0, the2755 * Minimum amount of LONG_RECORD to fetch. If &lt;0, the
2752 * <code>value</code> will contain just the descriptor portion.2756 * <code>value</code> will contain just the descriptor portion.
2753 * @param bufferForPruning2757 * @param bufferForPruning
2754 * If not <code>null</code> and <code>Value</code> did contain2758 * If not <code>null</code> and <code>Value</code> did contain an
2755 * an MVV, call {@link Buffer#enqueuePruningAction(int)}.2759 * MVV, call {@link Buffer#enqueuePruningAction(int)}.
2756 * @throws PersistitException2760 * @throws PersistitException
2757 * As thrown from any internal method.2761 * As thrown from any internal method.
2758 * @return <code>true</code> if the value was visible.2762 * @return <code>true</code> if the value was visible.
2759 */2763 */
2760 private boolean fetchFromValueInternal(Value value, int minimumBytes, Buffer bufferForPruning) throws PersistitException {2764 private boolean fetchFromValueInternal(Value value, int minimumBytes, Buffer bufferForPruning)
2765 throws PersistitException {
2761 boolean visible = true;2766 boolean visible = true;
2762 /*2767 /*
2763 * We must fetch the full LONG_RECORD, if needed, while buffer is2768 * We must fetch the full LONG_RECORD, if needed, while buffer is
@@ -3042,7 +3047,7 @@
30423047
3043 /**3048 /**
3044 * Removes all records with keys falling between <code>key1</code> and3049 * Removes all records with keys falling between <code>key1</code> and
3045 * </code>key2</code>, lefty-inclusive. Validity checks and Key value3050 * </code>key2</code>, left-inclusive. Validity checks and Key value
3046 * adjustments have been done by caller - this method does the work.3051 * adjustments have been done by caller - this method does the work.
3047 * 3052 *
3048 * @param key13053 * @param key1
@@ -3145,7 +3150,6 @@
3145 boolean result = false;3150 boolean result = false;
31463151
3147 boolean deallocationRequired = true; // assume until proven false3152 boolean deallocationRequired = true; // assume until proven false
3148 boolean deferredReindexRequired = false;
3149 boolean tryQuickDelete = true;3153 boolean tryQuickDelete = true;
31503154
3151 if (!_ignoreTransactions) {3155 if (!_ignoreTransactions) {
@@ -3227,9 +3231,6 @@
3227 + " failed to get writer claim on " + _tree);3231 + " failed to get writer claim on " + _tree);
3228 }3232 }
3229 treeClaimAcquired = true;3233 treeClaimAcquired = true;
3230 _tree.bumpGeneration();
3231 // Because we actually haven't changed anything yet.
3232 _cachedTreeGeneration++;
3233 }3234 }
3234 //3235 //
3235 // Need to redo this check now that we have a3236 // Need to redo this check now that we have a
@@ -3265,12 +3266,6 @@
3265 lc._rightBuffer = buffer;3266 lc._rightBuffer = buffer;
3266 lc._rightFoundAt = foundAt2;3267 lc._rightFoundAt = foundAt2;
3267 } else {3268 } else {
3268 //
3269 // Since we are spanning pages we need an
3270 // exclusive claim on the tree to prevent
3271 // an insertion from propagating upward through
3272 // the deletion range.
3273 //
3274 pageAddr2 = buffer.getRightSibling();3269 pageAddr2 = buffer.getRightSibling();
3275 samePage = false;3270 samePage = false;
3276 }3271 }
@@ -3358,11 +3353,13 @@
3358 _volume.getStructure().harvestLongRecords(buffer1, foundAt1, Integer.MAX_VALUE);3353 _volume.getStructure().harvestLongRecords(buffer1, foundAt1, Integer.MAX_VALUE);
3359 _volume.getStructure().harvestLongRecords(buffer2, 0, foundAt2);3354 _volume.getStructure().harvestLongRecords(buffer2, 0, foundAt2);
33603355
3356 Debug.$assert0.t(_tree.isMine() && buffer1.isMine() && buffer2.isMine());
3361 boolean rebalanced = buffer1.join(buffer2, foundAt1, foundAt2, _spareKey1, _spareKey2,3357 boolean rebalanced = buffer1.join(buffer2, foundAt1, foundAt2, _spareKey1, _spareKey2,
3362 _joinPolicy);3358 _joinPolicy);
3363 if (buffer1.isDataPage()) {3359 if (buffer1.isDataPage()) {
3364 _tree.bumpChangeCount();3360 _tree.bumpChangeCount();
3365 }3361 }
3362
3366 buffer1.setDirtyAtTimestamp(timestamp);3363 buffer1.setDirtyAtTimestamp(timestamp);
3367 buffer2.setDirtyAtTimestamp(timestamp);3364 buffer2.setDirtyAtTimestamp(timestamp);
33683365
@@ -3419,9 +3416,11 @@
3419 }3416 }
3420 }3417 }
3421 if (needsReindex) {3418 if (needsReindex) {
3422 lc._deferredReindexPage = buffer2.getPageAddress();3419 _spareKey1.copyTo(_spareKey2);
3423 lc._deferredReindexChangeCount = buffer2.getGeneration();3420 _value.setPointerValue(buffer2.getPageAddress());
3424 deferredReindexRequired = true;3421 _value.setPointerPageType(buffer2.getPageType());
3422
3423 storeInternal(_spareKey2, _value, level + 1, StoreOptions.NONE);
3425 needsReindex = false;3424 needsReindex = false;
3426 }3425 }
3427 }3426 }
@@ -3449,7 +3448,7 @@
3449 }3448 }
3450 break;3449 break;
3451 } catch (RetryException re) {3450 } catch (RetryException re) {
3452 // handled below3451 // handled below by releasing claims and retrying
3453 } finally {3452 } finally {
3454 //3453 //
3455 // Release all buffers.3454 // Release all buffers.
@@ -3459,10 +3458,17 @@
3459 }3458 }
34603459
3461 if (treeClaimAcquired) {3460 if (treeClaimAcquired) {
3461 if (treeWriterClaimRequired) {
3462 _tree.bumpGeneration();
3463 }
3462 _treeHolder.release();3464 _treeHolder.release();
3463 treeClaimAcquired = false;3465 treeClaimAcquired = false;
3464 }3466 }
3465 }3467 }
3468 /*
3469 * Having released all prior claims, now acquire an exclusive
3470 * claim on the Tree.
3471 */
3466 if (treeWriterClaimRequired) {3472 if (treeWriterClaimRequired) {
3467 if (!_treeHolder.claim(true)) {3473 if (!_treeHolder.claim(true)) {
3468 Debug.$assert0.t(false);3474 Debug.$assert0.t(false);
@@ -3490,51 +3496,11 @@
3490 deallocationRequired = false;3496 deallocationRequired = false;
3491 break;3497 break;
3492 }3498 }
3493 while (deferredReindexRequired) {
3494 Buffer buffer = null;
3495 try {
3496 for (int level = _cacheDepth; --level >= 0;) {
3497 LevelCache lc = _levelCache[level];
3498 if (lc._deferredReindexPage != 0) {
3499 if (!treeClaimAcquired) {
3500 if (!_treeHolder.claim(treeWriterClaimRequired)) {
3501 Debug.$assert0.t(false);
3502 throw new InUseException("Thread " + Thread.currentThread().getName()
3503 + " failed to get writer claim on " + _tree);
3504 }
3505 treeClaimAcquired = true;
3506 }
3507
3508 long deferredPage = lc._deferredReindexPage;
3509 buffer = _pool.get(_volume, deferredPage, false, true);
3510 if (buffer.getGeneration() == lc._deferredReindexChangeCount) {
3511 checkPageType(buffer, level + PAGE_TYPE_DATA, false);
3512 buffer.nextKey(_spareKey2, buffer.toKeyBlock(0));
3513 _value.setPointerValue(buffer.getPageAddress());
3514 _value.setPointerPageType(buffer.getPageType());
3515 storeInternal(_spareKey2, _value, level + 1, StoreOptions.NONE);
3516 } else {
3517 _persistit.getLogBase().unindexedPage.log(deferredPage, _volume, _tree.getName());
3518 }
3519 lc._deferredReindexPage = 0;
3520 buffer.releaseTouched();
3521 buffer = null;
3522 }
3523 }
3524 deferredReindexRequired = false;
3525 } catch (RetryException re) {
3526 // can this even be thrown here?
3527 } finally {
3528 if (buffer != null) {
3529 buffer.releaseTouched();
3530 buffer = null;
3531 }
3532 }
3533 }
3534
3535 } finally {3499 } finally {
3536 if (treeClaimAcquired) {3500 if (treeClaimAcquired) {
3537 _tree.bumpGeneration();3501 if (treeWriterClaimRequired) {
3502 _tree.bumpGeneration();
3503 }
3538 _treeHolder.release();3504 _treeHolder.release();
3539 treeClaimAcquired = false;3505 treeClaimAcquired = false;
3540 }3506 }
35413507
=== modified file 'src/main/java/com/persistit/IntegrityCheck.java'
--- src/main/java/com/persistit/IntegrityCheck.java 2012-06-26 19:36:26 +0000
+++ src/main/java/com/persistit/IntegrityCheck.java 2012-06-29 21:06:19 +0000
@@ -339,7 +339,14 @@
339 }339 }
340340
341 private void addFault(String description, long page, int level, int position) {341 private void addFault(String description, long page, int level, int position) {
342 Fault fault = new Fault(resourceName(), this, description, page, level, position);342 Fault fault = new Fault(resourceName(), this, description, page, _treeDepth, level, position);
343 if (_faults.size() < MAX_FAULTS)
344 _faults.add(fault);
345 postMessage(fault.toString(), LOG_VERBOSE);
346 }
347
348 private void addGarbageFault(String description, long page, int level, int position) {
349 Fault fault = new Fault(resourceName(), this, description, page, 3, level, position);
343 if (_faults.size() < MAX_FAULTS)350 if (_faults.size() < MAX_FAULTS)
344 _faults.add(fault);351 _faults.add(fault);
345 postMessage(fault.toString(), LOG_VERBOSE);352 postMessage(fault.toString(), LOG_VERBOSE);
@@ -645,10 +652,10 @@
645 int _depth;652 int _depth;
646 int _position;653 int _position;
647654
648 Fault(String treeName, IntegrityCheck work, String description, long page, int level, int position) {655 Fault(String treeName, IntegrityCheck work, String description, long page, int depth, int level, int position) {
649 _treeName = treeName;656 _treeName = treeName;
650 _description = description;657 _description = description;
651 _depth = work._treeDepth;658 _depth = depth;
652 _path = new long[_depth - level];659 _path = new long[_depth - level];
653 for (int index = _depth; --index > level;) {660 for (int index = _depth; --index > level;) {
654 if (index >= work._edgeBuffers.length) {661 if (index >= work._edgeBuffers.length) {
@@ -657,7 +664,7 @@
657 _path[index - level] = work._edgePages[index];664 _path[index - level] = work._edgePages[index];
658 }665 }
659 }666 }
660 _path[level] = page;667 _path[0] = page;
661 _level = level;668 _level = level;
662 _depth = work._treeDepth;669 _depth = work._treeDepth;
663 _position = position;670 _position = position;
@@ -994,11 +1001,11 @@
994 private void checkGarbagePage(Buffer garbageBuffer) throws PersistitException {1001 private void checkGarbagePage(Buffer garbageBuffer) throws PersistitException {
995 long page = garbageBuffer.getPageAddress();1002 long page = garbageBuffer.getPageAddress();
996 if (!garbageBuffer.isGarbagePage()) {1003 if (!garbageBuffer.isGarbagePage()) {
997 addFault("Unexpected page type " + garbageBuffer.getPageType() + " expected a garbage page", page, 1, 0);1004 addGarbageFault("Unexpected page type " + garbageBuffer.getPageType() + " expected a garbage page", page, 1, 0);
998 return;1005 return;
999 }1006 }
1000 if (_usedPageBits.get(page)) {1007 if (_usedPageBits.get(page)) {
1001 addFault("Garbage page is referenced by multiple parents", page, 1, 0);1008 addGarbageFault("Garbage page is referenced by multiple parents", page, 1, 0);
1002 return;1009 return;
1003 }1010 }
10041011
@@ -1007,7 +1014,7 @@
1007 int size = garbageBuffer.getBufferSize();1014 int size = garbageBuffer.getBufferSize();
1008 int count = (size - next) / Buffer.GARBAGE_BLOCK_SIZE;1015 int count = (size - next) / Buffer.GARBAGE_BLOCK_SIZE;
1009 if (count * Buffer.GARBAGE_BLOCK_SIZE != (size - next)) {1016 if (count * Buffer.GARBAGE_BLOCK_SIZE != (size - next)) {
1010 addFault("Garbage page is malformed: _alloc=" + next + " is not at a multiple of "1017 addGarbageFault("Garbage page is malformed: _alloc=" + next + " is not at a multiple of "
1011 + Buffer.GARBAGE_BLOCK_SIZE + " bytes", page, 1, 0);1018 + Buffer.GARBAGE_BLOCK_SIZE + " bytes", page, 1, 0);
1012 }1019 }
1013 _usedPageBits.set(page, true);1020 _usedPageBits.set(page, true);
@@ -1026,12 +1033,12 @@
1026 _edgePages[2] = page;1033 _edgePages[2] = page;
1027 while (page != 0 && page != right) {1034 while (page != 0 && page != right) {
1028 if (_usedPageBits.get(page)) {1035 if (_usedPageBits.get(page)) {
1029 addFault("Page on garbage chain is referenced by multiple parents", page, 3, 0);1036 addGarbageFault("Page on garbage chain is referenced by multiple parents", page, 0, 0);
1030 return;1037 return;
1031 }1038 }
1032 Buffer buffer = getPage(page);1039 Buffer buffer = getPage(page);
1033 if (!buffer.isDataPage() && !buffer.isIndexPage() && !buffer.isLongRecordPage()) {1040 if (!buffer.isDataPage() && !buffer.isIndexPage() && !buffer.isLongRecordPage()) {
1034 addFault("Page of type " + buffer.getPageTypeName() + " found on garbage page", page, 3, 0);1041 addGarbageFault("Page of type " + buffer.getPageTypeName() + " found on garbage page", page, 0, 0);
1035 }1042 }
1036 _counters._garbagePageCount++;1043 _counters._garbagePageCount++;
1037 _pagesVisited++;1044 _pagesVisited++;
10381045
=== modified file 'src/main/java/com/persistit/VolumeStorageV2.java'
--- src/main/java/com/persistit/VolumeStorageV2.java 2012-06-22 19:07:13 +0000
+++ src/main/java/com/persistit/VolumeStorageV2.java 2012-06-29 21:06:19 +0000
@@ -363,6 +363,7 @@
363 _headBuffer = _volume.getStructure().getPool().get(_volume, 0, true, false);363 _headBuffer = _volume.getStructure().getPool().get(_volume, 0, true, false);
364 boolean truncated = false;364 boolean truncated = false;
365 try {365 try {
366 _headBuffer.init(Buffer.PAGE_TYPE_HEAD);
366 _headBuffer.setFixed();367 _headBuffer.setFixed();
367368
368 initMetaData(_headBuffer.getBytes());369 initMetaData(_headBuffer.getBytes());
369370
=== modified file 'src/main/java/com/persistit/exception/RetryException.java'
--- src/main/java/com/persistit/exception/RetryException.java 2012-05-25 18:50:59 +0000
+++ src/main/java/com/persistit/exception/RetryException.java 2012-06-29 21:06:19 +0000
@@ -30,7 +30,4 @@
3030
31 public final static RetryException SINGLE = new RetryException();31 public final static RetryException SINGLE = new RetryException();
3232
33 public RetryException() {
34
35 }
36}33}
3734
=== modified file 'src/main/java/com/persistit/util/Debug.java'
--- src/main/java/com/persistit/util/Debug.java 2012-06-04 18:58:23 +0000
+++ src/main/java/com/persistit/util/Debug.java 2012-06-29 21:06:19 +0000
@@ -106,6 +106,32 @@
106 }106 }
107 System.err.println("Debug " + sb.toString());107 System.err.println("Debug " + sb.toString());
108 }108 }
109
110 public static String trace(int from, int to) {
111 return " " + Thread.currentThread().getName() + " {" + Debug.callStack(from + 2, to + 2) + "}";
112 }
113
114 public static String callStack(final int from, final int to) {
115 RuntimeException exception = new RuntimeException();
116 exception.fillInStackTrace();
117 StackTraceElement[] elements = exception.getStackTrace();
118 int a = Math.max(0, from);
119 int b = Math.min(to, elements.length);
120 StringBuilder sb = new StringBuilder();
121 for (int index = b; index >= a; index--) {
122 StackTraceElement t = exception.getStackTrace()[index];
123 if (index != b) {
124 sb.append("->");
125 }
126 sb.append(t.getClassName());
127 sb.append('#');
128 sb.append(t.getMethodName());
129 sb.append('[');
130 sb.append(t.getLineNumber());
131 sb.append("]");
132 }
133 return sb.toString();
134 }
109135
110 /**136 /**
111 * Set the suspend flag so that callers to the suspend method either do or137 * Set the suspend flag so that callers to the suspend method either do or
112138
=== modified file 'src/main/java/com/persistit/util/SequencerConstants.java'
--- src/main/java/com/persistit/util/SequencerConstants.java 2012-05-25 18:50:59 +0000
+++ src/main/java/com/persistit/util/SequencerConstants.java 2012-06-29 21:06:19 +0000
@@ -97,4 +97,5 @@
97 int LONG_RECORD_ALLOCATE_A = allocate("LONG_RECORD_ALLOCATE_A");97 int LONG_RECORD_ALLOCATE_A = allocate("LONG_RECORD_ALLOCATE_A");
98 int LONG_RECORD_ALLOCATE_B = allocate("LONG_RECORD_ALLOCATE_B");98 int LONG_RECORD_ALLOCATE_B = allocate("LONG_RECORD_ALLOCATE_B");
99 int[][] LONG_RECORD_ALLOCATE_SCHEDULED = new int[][]{array(LONG_RECORD_ALLOCATE_B), array(LONG_RECORD_ALLOCATE_A, LONG_RECORD_ALLOCATE_B)};99 int[][] LONG_RECORD_ALLOCATE_SCHEDULED = new int[][]{array(LONG_RECORD_ALLOCATE_B), array(LONG_RECORD_ALLOCATE_A, LONG_RECORD_ALLOCATE_B)};
100
100}101}
101102
=== modified file 'src/main/java/com/persistit/util/Util.java'
--- src/main/java/com/persistit/util/Util.java 2012-05-25 18:50:59 +0000
+++ src/main/java/com/persistit/util/Util.java 2012-06-29 21:06:19 +0000
@@ -234,6 +234,14 @@
234 return format(Long.toString(i), width, true);234 return format(Long.toString(i), width, true);
235 }235 }
236236
237 public static String abridge(final String s, final int maxLength) {
238 if (s.length() > maxLength) {
239 return s.substring(0, maxLength - 3) + "...";
240 } else {
241 return s;
242 }
243 }
244
237 public static boolean equalsByteSubarray(byte[] source, int next, byte[] target) {245 public static boolean equalsByteSubarray(byte[] source, int next, byte[] target) {
238 return equalsByteSubarray(source, next, target, 0, target.length);246 return equalsByteSubarray(source, next, target, 0, target.length);
239 }247 }
240248
=== added file 'src/test/java/com/persistit/Bug1017957Test.java'
--- src/test/java/com/persistit/Bug1017957Test.java 1970-01-01 00:00:00 +0000
+++ src/test/java/com/persistit/Bug1017957Test.java 2012-06-29 21:06:19 +0000
@@ -0,0 +1,235 @@
1/**
2 * Copyright © 2012 Akiban Technologies, Inc. All rights reserved.
3 *
4 * This program is free software: you can redistribute it and/or modify
5 * it under the terms of the GNU Affero General Public License as
6 * published by the Free Software Foundation, version 3 (only) of the
7 * License.
8 *
9 * This program is distributed in the hope that it will be useful,
10 * but WITHOUT ANY WARRANTY; without even the implied warranty of
11 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 * GNU Affero General Public License for more details.
13 *
14 * You should have received a copy of the GNU Affero General Public License
15 * along with this program. If not, see <http://www.gnu.org/licenses/>.
16 *
17 * This program may also be available under different license terms. For more
18 * information, see www.akiban.com or contact licensing@akiban.com.
19 */
20
21package com.persistit;
22
23import static org.junit.Assert.assertEquals;
24
25import java.io.PrintWriter;
26import java.util.Properties;
27import java.util.concurrent.atomic.AtomicInteger;
28
29import org.junit.Test;
30
31import com.persistit.mxbeans.ManagementMXBean;
32import com.persistit.unit.PersistitUnitTestCase;
33import com.persistit.unit.UnitTestProperties;
34
35/**
36 * https://bugs.launchpad.net/akiban-persistit/+bug/1017957
37 *
38 * During the past week the 8-hour stress test suite has generated several
39 * CorruptVolumeExceptions and other related phenomena. Examples:
40 *
41 * Stress6 [main] FAILED: com.persistit.exception.CorruptVolumeException: Volume
42 * persistit(/tmp/persistit_tests/persistit) level=0 page=15684
43 * initialPage=57164
44 * key=<{"stress6",98,5,"xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx"}> walked
45 * right more than 50 pages last page visited=81324 at
46 * com.persistit.Exchange.corrupt(Exchange.java:3884) at
47 * com.persistit.Exchange.searchLevel(Exchange.java:1250) at
48 * com.persistit.Exchange.searchTree(Exchange.java:1125) at
49 * com.persistit.Exchange.storeInternal(Exchange.java:1443) at
50 * com.persistit.Exchange.store(Exchange.java:1294) at
51 * com.persistit.Exchange.store(Exchange.java:2534) at
52 * com.persistit.stress.unit.Stress6.executeTest(Stress6.java:98) at
53 * com.persistit.stress.AbstractStressTest.run(AbstractStressTest.java:93) at
54 * java.lang.Thread.run(Thread.java:662)
55 *
56 * Stress2txn [main] FAILED: com.persistit.exception.RebalanceException at
57 * com.persistit.Buffer.join(Buffer.java:2523) at
58 * com.persistit.Exchange.raw_removeKeyRangeInternal(Exchange.java:3367) at
59 * com.persistit.Exchange.removeKeyRangeInternal(Exchange.java:3070) at
60 * com.persistit.Exchange.removeInternal(Exchange.java:2999) at
61 * com.persistit.Exchange.remove(Exchange.java:2927) at
62 * com.persistit.stress.unit.Stress2txn.executeTest(Stress2txn.java:231) at
63 * com.persistit.stress.AbstractStressTest.run(AbstractStressTest.java:93) at
64 * java.lang.Thread.run(Thread.java:662)
65 *
66 * Stress2txn [main] FAILED: com.persistit.exception.CorruptVolumeException:
67 * LONG_RECORD chain is invalid at page 111919 - invalid page type: Page 111,919
68 * in volume persistit(/tmp/persistit_tests/persistit) at index 1,559
69 * timestamp=909,787,072 status=vr1 type=Data at
70 * com.persistit.LongRecordHelper.corrupt(LongRecordHelper.java:243) at
71 * com.persistit.LongRecordHelper.fetchLongRecord(LongRecordHelper.java:103) at
72 * com.persistit.Exchange.fetchFixupForLongRecords(Exchange.java:2841) at
73 * com.persistit.Exchange.fetchFromValueInternal(Exchange.java:2778) at
74 * com.persistit.Exchange.fetchFromBufferInternal(Exchange.java:2747) at
75 * com.persistit.Exchange.traverse(Exchange.java:2157) at
76 * com.persistit.Exchange.traverse(Exchange.java:1960) at
77 * com.persistit.Exchange.traverse(Exchange.java:1897) at
78 * com.persistit.Exchange.next(Exchange.java:2330) at
79 * com.persistit.stress.unit.Stress2txn.executeTest(Stress2txn.java:188) at
80 * com.persistit.stress.AbstractStressTest.run(AbstractStressTest.java:93) at
81 * java.lang.Thread.run(Thread.java:662)
82 *
83 * Bug mechanism #1:
84 *
85 * An obscure path through Exchange#raw_removeKeyRangeInternal inserts a
86 * key-pointer pair into an index page. It does so after removing all claims on
87 * pages and the tree itself. After removing claims, before inserting the
88 * key-pointer pair we believe the page itself gets put unto a garbage chain. So
89 * after the re-insertion, the index page now has a pointer to a page that will
90 * be reused and will contain unrelated data.
91 *
92 *
93 * Bug mechanism #2: An obscure path through Exchange#raw_removeKeyRangeInternal
94 * performs a structure delete (i.e., joins one more pairs of pages) but fails
95 * to bump the Tree generation. The allows use of a stale LevelCache array.
96 *
97 * This test procedure exhibited both bug mechanisms reliably within 10 seconds
98 * prior to fixing the code. We also implemented a test method based on the
99 * ThreadSequencer to precisely elaborate the sequence of interactions between
100 * two threads that cause the failure. However, the bug fix eliminates the code
101 * path that allows the sequencer to work, so the test was removed.
102 *
103 * @author peter
104 *
105 */
106public class Bug1017957Test extends PersistitUnitTestCase {
107
108 @Override
109 protected Properties getProperties(boolean cleanup) {
110 return UnitTestProperties.getBiggerProperties(cleanup);
111 }
112
113 private final long STRESS_NANOS = 10L * 1000000000L;
114
115 /**
116 *
117 * @throws Exception
118 */
119 @Test
120 public void induceCorruptionByStress() throws Exception {
121 final long expiresAt = System.nanoTime() + STRESS_NANOS;
122 final AtomicInteger totalErrors = new AtomicInteger();
123 Thread t1 = new Thread(new Runnable() {
124 public void run() {
125 int count = 0;
126 int errors = 0;
127 try {
128 Exchange ex = _persistit.getExchange("persistit", "Bug1017957Test", true);
129 while (System.nanoTime() < expiresAt) {
130 try {
131 Key key = createUnsafeStructure(ex);
132 removeInterestingKey(ex, key);
133 if (++count % 5000 == 0) {
134 System.out.printf("T1 iterations %,d\n", count);
135 }
136 } catch (Exception e) {
137 if (++errors < 10) {
138 e.printStackTrace();
139 }
140 totalErrors.incrementAndGet();
141 }
142 }
143 } catch (Exception e) {
144 throw new RuntimeException(e);
145 }
146 }
147 });
148
149 Thread t2 = new Thread(new Runnable() {
150 public void run() {
151 int count = 0;
152 int errors = 0;
153 try {
154 Exchange ex = _persistit.getExchange("persistit", "Bug1017957Test", true);
155 while (System.nanoTime() < expiresAt) {
156 try {
157 removeCoveringRange(ex);
158 insertOtherStuff(ex);
159 if (++count % 5000 == 0) {
160 System.out.printf("T2 iterations %,d\n", count);
161 }
162 } catch (Exception e) {
163 if (++errors < 10) {
164 e.printStackTrace();
165 }
166 totalErrors.incrementAndGet();
167 }
168 }
169 } catch (Exception e) {
170 if (++errors < 10) {
171 e.printStackTrace();
172 }
173 }
174 }
175 });
176
177 t1.start();
178 t2.start();
179 t1.join();
180 t2.join();
181
182 IntegrityCheck icheck = new IntegrityCheck(_persistit);
183 icheck.setMessageLogVerbosity(Task.LOG_VERBOSE);
184 icheck.setMessageWriter(new PrintWriter(System.out));
185 icheck.checkVolume(_persistit.getVolume("persistit"));
186 System.out.printf("\nTotal errors %d", totalErrors.get());
187 assertEquals("Corrupt volume", 0, icheck.getFaults().length);
188 assertEquals("Exception occurred", 0, totalErrors.get());
189 }
190
191 /**
192 * Create a B-Tree with a structure that will induce a deferred index
193 * insertion on removal of key. We need an index page that's pretty full
194 * such that removing a key and inserting a different one will result in
195 * splitting the index page.
196 *
197 * @throws Exception
198 */
199 private Key createUnsafeStructure(final Exchange ex) throws Exception {
200 Key result = null;
201 final String v = createString(5500); // less than long record
202 final String k = createString(1040);
203 for (int i = 1000; i < 1019; i++) {
204 if (i == 1009) {
205 ex.clear().append(i).append(k.substring(0, 20));
206 ex.getValue().put("interesting");
207 ex.store();
208 result = new Key(ex.getKey());
209 }
210 ex.clear().append(i).append(k);
211 ex.getValue().put(v);
212 ex.store();
213 }
214 return result;
215 }
216
217 private void removeInterestingKey(final Exchange ex, final Key interestingKey) throws Exception {
218 interestingKey.copyTo(ex.getKey());
219 ex.remove();
220 }
221
222 private void removeCoveringRange(final Exchange ex) throws Exception {
223 final Key key1 = new Key(_persistit).append(1005);
224 final Key key2 = new Key(_persistit).append(1015);
225 ex.removeKeyRange(key1, key2);
226 }
227
228 private void insertOtherStuff(final Exchange ex) throws Exception {
229 for (int k = 0; k < 100; k++) {
230 ex.clear().append(1009).append(k).append(RED_FOX);
231 ex.getValue().put(RED_FOX);
232 ex.store();
233 }
234 }
235}
0236
=== modified file 'src/test/java/com/persistit/IntegrityCheckTest.java'
--- src/test/java/com/persistit/IntegrityCheckTest.java 2012-05-25 18:50:59 +0000
+++ src/test/java/com/persistit/IntegrityCheckTest.java 2012-06-29 21:06:19 +0000
@@ -211,6 +211,16 @@
211 _persistit.getTransactionIndex().cleanup();211 _persistit.getTransactionIndex().cleanup();
212 assertEquals(0, _persistit.getTransactionIndex().getAbortedCount());212 assertEquals(0, _persistit.getTransactionIndex().getAbortedCount());
213 }213 }
214
215 @Test
216 public void testCorruptGarbageChain() throws PersistitException {
217 final Exchange ex = _persistit.getExchange(_volumeName, "mvv", true);
218 nonTransactionalStore(ex);
219 corrupt4(ex);
220 IntegrityCheck icheck = icheck();
221 icheck.checkVolume(ex.getVolume());
222 assertTrue(icheck.getFaults().length > 0);
223 }
214224
215 private String key(final int i) {225 private String key(final int i) {
216 return String.format("%05d%s", i, RED_FOX);226 return String.format("%05d%s", i, RED_FOX);
@@ -314,6 +324,19 @@
314 buffer.setDirtyAtTimestamp(_persistit.getTimestampAllocator().updateTimestamp());324 buffer.setDirtyAtTimestamp(_persistit.getTimestampAllocator().updateTimestamp());
315 buffer.release();325 buffer.release();
316 }326 }
327
328 /**
329 * Corrupts garbage chain by adding a live data chain to it
330 * @throw PersistitException
331 */
332 private void corrupt4(final Exchange ex) throws PersistitException {
333 Key key = ex.getKey();
334 ex.clear().to(key(500));
335 Buffer copy = ex.fetchBufferCopy(0);
336 Buffer buffer = ex.getBufferPool().get(ex.getVolume(), copy.getPageAddress(), true, true);
337 ex.getVolume().getStructure().deallocateGarbageChain(buffer.getPageAddress(), buffer.getRightSibling());
338 buffer.release();
339 }
317340
318 private IntegrityCheck icheck() {341 private IntegrityCheck icheck() {
319 IntegrityCheck icheck = new IntegrityCheck(_persistit);342 IntegrityCheck icheck = new IntegrityCheck(_persistit);

Subscribers

People subscribed via source and target branches