Merge lp:~pbeaman/akiban-persistit/fix_1017957_stress_tests_corruption into lp:akiban-persistit
- fix_1017957_stress_tests_corruption
- Merge into trunk
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 | ||||
Related bugs: |
|
Reviewer | Review Type | Date Requested | Status |
---|---|---|---|
Nathan Williams | Approve | ||
Review via email: mp+112647@code.launchpad.net |
Commit message
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 : | # |
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
1 | === modified file 'src/main/java/com/persistit/Buffer.java' | |||
2 | --- src/main/java/com/persistit/Buffer.java 2012-06-25 15:41:14 +0000 | |||
3 | +++ src/main/java/com/persistit/Buffer.java 2012-06-29 21:06:19 +0000 | |||
4 | @@ -28,7 +28,6 @@ | |||
5 | 28 | 28 | ||
6 | 29 | import java.nio.ByteBuffer; | 29 | import java.nio.ByteBuffer; |
7 | 30 | import java.util.ArrayList; | 30 | import java.util.ArrayList; |
8 | 31 | import java.util.BitSet; | ||
9 | 32 | import java.util.List; | 31 | import java.util.List; |
10 | 33 | import java.util.Set; | 32 | import java.util.Set; |
11 | 34 | 33 | ||
12 | @@ -269,7 +268,7 @@ | |||
13 | 269 | public final static int MAX_KEY_RATIO = 16; | 268 | public final static int MAX_KEY_RATIO = 16; |
14 | 270 | 269 | ||
15 | 271 | private final static int BINARY_SEARCH_THRESHOLD = 6; | 270 | private final static int BINARY_SEARCH_THRESHOLD = 6; |
17 | 272 | 271 | ||
18 | 273 | abstract static class VerifyVisitor { | 272 | abstract static class VerifyVisitor { |
19 | 274 | 273 | ||
20 | 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, |
21 | @@ -330,7 +329,7 @@ | |||
22 | 330 | * _byteBuffer. | 329 | * _byteBuffer. |
23 | 331 | */ | 330 | */ |
24 | 332 | private byte[] _bytes; | 331 | private byte[] _bytes; |
26 | 333 | 332 | ||
27 | 334 | /** | 333 | /** |
28 | 335 | * FastIndex structure used for rapid page searching | 334 | * FastIndex structure used for rapid page searching |
29 | 336 | */ | 335 | */ |
30 | @@ -2318,6 +2317,7 @@ | |||
31 | 2318 | assertVerify(); | 2317 | assertVerify(); |
32 | 2319 | rightSibling.assertVerify(); | 2318 | rightSibling.assertVerify(); |
33 | 2320 | } | 2319 | } |
34 | 2320 | |||
35 | 2321 | return whereInserted; | 2321 | return whereInserted; |
36 | 2322 | } | 2322 | } |
37 | 2323 | 2323 | ||
38 | @@ -2594,6 +2594,7 @@ | |||
39 | 2594 | buffer.assertVerify(); | 2594 | buffer.assertVerify(); |
40 | 2595 | } | 2595 | } |
41 | 2596 | } | 2596 | } |
42 | 2597 | |||
43 | 2597 | return result; | 2598 | return result; |
44 | 2598 | } | 2599 | } |
45 | 2599 | 2600 | ||
46 | @@ -2815,7 +2816,7 @@ | |||
47 | 2815 | } | 2816 | } |
48 | 2816 | 2817 | ||
49 | 2817 | synchronized void invalidateFastIndex() { | 2818 | synchronized void invalidateFastIndex() { |
51 | 2818 | _fastIndex.invalidate(); | 2819 | _fastIndex.invalidate(); |
52 | 2819 | } | 2820 | } |
53 | 2820 | 2821 | ||
54 | 2821 | synchronized FastIndex getFastIndex() { | 2822 | synchronized FastIndex getFastIndex() { |
55 | @@ -3775,7 +3776,7 @@ | |||
56 | 3775 | 3776 | ||
57 | 3776 | public String toString() { | 3777 | public String toString() { |
58 | 3777 | if (_toStringDebug) { | 3778 | if (_toStringDebug) { |
60 | 3778 | return toStringDetail(-1); | 3779 | return toStringDetail(); |
61 | 3779 | } | 3780 | } |
62 | 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, |
63 | 3781 | _poolIndex, _timestamp, getStatusDisplayString(), getPageTypeName()); | 3782 | _poolIndex, _timestamp, getStatusDisplayString(), getPageTypeName()); |
64 | @@ -3785,7 +3786,7 @@ | |||
65 | 3785 | * @return a human-readable inventory of the contents of this buffer | 3786 | * @return a human-readable inventory of the contents of this buffer |
66 | 3786 | */ | 3787 | */ |
67 | 3787 | public String toStringDetail() { | 3788 | public String toStringDetail() { |
69 | 3788 | return toStringDetail(-1); | 3789 | return toStringDetail(-1, 42, 42, 0, true); |
70 | 3789 | } | 3790 | } |
71 | 3790 | 3791 | ||
72 | 3791 | /** | 3792 | /** |
73 | @@ -3795,7 +3796,8 @@ | |||
74 | 3795 | * the one that points to findPointer. This provides a way to | 3796 | * the one that points to findPointer. This provides a way to |
75 | 3796 | * quickly find pointer paths in pages. | 3797 | * quickly find pointer paths in pages. |
76 | 3797 | */ | 3798 | */ |
78 | 3798 | String toStringDetail(final long findPointer) { | 3799 | String toStringDetail(final long findPointer, final int maxKeyDisplayLength, final int maxValueDisplayLength, |
79 | 3800 | final int contextLines, final boolean all) { | ||
80 | 3799 | final StringBuilder sb = new StringBuilder(String.format( | 3801 | final StringBuilder sb = new StringBuilder(String.format( |
81 | 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, |
82 | 3801 | getStatusDisplayString(), getPageTypeName())); | 3803 | getStatusDisplayString(), getPageTypeName())); |
83 | @@ -3805,52 +3807,53 @@ | |||
84 | 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 " |
85 | 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, |
86 | 3807 | _keyBlockEnd, getTimestamp(), getGeneration(), getRightSibling(), _pool.hashIndex(_vol, _page))); | 3809 | _keyBlockEnd, getTimestamp(), getGeneration(), getRightSibling(), _pool.hashIndex(_vol, _page))); |
87 | 3808 | |||
88 | 3809 | try { | 3810 | try { |
89 | 3810 | final Key key = new Key(_persistit); | 3811 | final Key key = new Key(_persistit); |
90 | 3811 | final Value value = new Value(_persistit); | 3812 | final Value value = new Value(_persistit); |
91 | 3812 | final RecordInfo[] records = getRecords(); | 3813 | final RecordInfo[] records = getRecords(); |
93 | 3813 | BitSet bits = new BitSet(); | 3814 | int foundPointerRecord = -1; |
94 | 3814 | if (isIndexPage() && findPointer >= 0) { | 3815 | if (isIndexPage() && findPointer >= 0) { |
95 | 3815 | for (int index = 0; index < records.length; index++) { | 3816 | for (int index = 0; index < records.length; index++) { |
96 | 3816 | if (records[index].getPointerValue() == findPointer) { | 3817 | if (records[index].getPointerValue() == findPointer) { |
98 | 3817 | bits.set(index); | 3818 | foundPointerRecord = index; |
99 | 3818 | } | 3819 | } |
100 | 3819 | } | 3820 | } |
101 | 3820 | } | 3821 | } |
103 | 3821 | boolean elisionMarked = false; | 3822 | boolean elision = false; |
104 | 3822 | for (int index = 0; index < records.length; index++) { | 3823 | for (int index = 0; index < records.length; index++) { |
105 | 3823 | RecordInfo r = records[index]; | 3824 | RecordInfo r = records[index]; |
106 | 3824 | r.getKeyState().copyTo(key); | 3825 | r.getKeyState().copyTo(key); |
112 | 3825 | if (isDataPage()) { | 3826 | String mark = " "; |
113 | 3826 | r.getValueState().copyTo(value); | 3827 | boolean selected = all | index < contextLines || index >= records.length - contextLines; |
114 | 3827 | sb.append(String.format("\n %5d: db=%3d ebc=%3d tb=%,5d [%,d]%s=[%,d]%s", r.getKbOffset(), r | 3828 | if (foundPointerRecord >= 0) { |
115 | 3828 | .getDb(), r.getEbc(), r.getTbOffset(), r.getKLength(), key, r.getValueState() | 3829 | selected |= (index >= foundPointerRecord - contextLines) |
116 | 3829 | .getEncodedBytes().length, abridge(value))); | 3830 | && (index <= foundPointerRecord + contextLines); |
117 | 3831 | if (index == foundPointerRecord) { | ||
118 | 3832 | mark = "*"; | ||
119 | 3833 | } | ||
120 | 3834 | } | ||
121 | 3835 | |||
122 | 3836 | if (selected) { | ||
123 | 3837 | if (elision) { | ||
124 | 3838 | sb.append(String.format("\n ...")); | ||
125 | 3839 | elision = false; | ||
126 | 3840 | } | ||
127 | 3841 | if (isDataPage()) { | ||
128 | 3842 | r.getValueState().copyTo(value); | ||
129 | 3843 | sb.append(String.format("\n%s %5d: db=%3d ebc=%3d tb=%,5d [%,d]%s=[%,d]%s", mark, r | ||
130 | 3844 | .getKbOffset(), r.getDb(), r.getEbc(), r.getTbOffset(), r.getKLength(), Util | ||
131 | 3845 | .abridge(key.toString(), maxKeyDisplayLength), | ||
132 | 3846 | r.getValueState().getEncodedBytes().length, Util.abridge(value.toString(), | ||
133 | 3847 | maxValueDisplayLength))); | ||
134 | 3848 | } else { | ||
135 | 3849 | sb.append(String.format("\n%s %5d: db=%3d ebc=%3d tb=%,5d [%,d]%s->%,d", mark, r | ||
136 | 3850 | .getKbOffset(), r.getDb(), r.getEbc(), r.getTbOffset(), r.getKLength(), Util | ||
137 | 3851 | .abridge(key.toString(), maxKeyDisplayLength), r.getPointerValue())); | ||
138 | 3852 | } | ||
139 | 3830 | } else { | 3853 | } else { |
162 | 3831 | boolean selected = true; | 3854 | elision = true; |
141 | 3832 | if (findPointer >= 0) { | ||
142 | 3833 | if (index > 2 && index < records.length - 2) { | ||
143 | 3834 | boolean bit = false; | ||
144 | 3835 | for (int p = index - 2; p < index + 3; p++) { | ||
145 | 3836 | if (bits.get(p)) { | ||
146 | 3837 | bit = true; | ||
147 | 3838 | } | ||
148 | 3839 | } | ||
149 | 3840 | selected &= bit; | ||
150 | 3841 | } | ||
151 | 3842 | } | ||
152 | 3843 | if (selected) { | ||
153 | 3844 | sb.append(String.format("\n %5d: db=%3d ebc=%3d tb=%,5d [%,d]%s->%,d", r.getKbOffset(), r | ||
154 | 3845 | .getDb(), r.getEbc(), r.getTbOffset(), r.getKLength(), key, r.getPointerValue())); | ||
155 | 3846 | elisionMarked = false; | ||
156 | 3847 | } else { | ||
157 | 3848 | if (!elisionMarked) { | ||
158 | 3849 | sb.append(String.format("\n ...")); | ||
159 | 3850 | elisionMarked = true; | ||
160 | 3851 | } | ||
161 | 3852 | } | ||
163 | 3853 | } | 3855 | } |
164 | 3856 | |||
165 | 3854 | } | 3857 | } |
166 | 3855 | } catch (Exception e) { | 3858 | } catch (Exception e) { |
167 | 3856 | sb.append(" - " + e); | 3859 | sb.append(" - " + e); |
168 | @@ -3875,15 +3878,6 @@ | |||
169 | 3875 | return sb.toString(); | 3878 | return sb.toString(); |
170 | 3876 | } | 3879 | } |
171 | 3877 | 3880 | ||
172 | 3878 | private String abridge(final Value value) { | ||
173 | 3879 | String s = value.toString(); | ||
174 | 3880 | if (s.length() > 120) { | ||
175 | 3881 | return s.substring(0, 120) + "..."; | ||
176 | 3882 | } else { | ||
177 | 3883 | return s; | ||
178 | 3884 | } | ||
179 | 3885 | } | ||
180 | 3886 | |||
181 | 3887 | String foundAtString(int p) { | 3881 | String foundAtString(int p) { |
182 | 3888 | StringBuilder sb = new StringBuilder("<"); | 3882 | StringBuilder sb = new StringBuilder("<"); |
183 | 3889 | sb.append(p & P_MASK); | 3883 | sb.append(p & P_MASK); |
184 | @@ -4316,4 +4310,5 @@ | |||
185 | 4316 | ++_mvvCount; | 4310 | ++_mvvCount; |
186 | 4317 | } | 4311 | } |
187 | 4318 | } | 4312 | } |
188 | 4313 | |||
189 | 4319 | } | 4314 | } |
190 | 4320 | 4315 | ||
191 | === modified file 'src/main/java/com/persistit/BufferPool.java' | |||
192 | --- src/main/java/com/persistit/BufferPool.java 2012-06-14 17:55:08 +0000 | |||
193 | +++ src/main/java/com/persistit/BufferPool.java 2012-06-29 21:06:19 +0000 | |||
194 | @@ -1054,6 +1054,7 @@ | |||
195 | 1054 | } | 1054 | } |
196 | 1055 | 1055 | ||
197 | 1056 | int selectDirtyBuffers(final int[] priorities, final BufferHolder[] holders) throws PersistitException { | 1056 | int selectDirtyBuffers(final int[] priorities, final BufferHolder[] holders) throws PersistitException { |
198 | 1057 | Debug.suspend(); | ||
199 | 1057 | int count = 0; | 1058 | int count = 0; |
200 | 1058 | final int clock = _clock.get(); | 1059 | final int clock = _clock.get(); |
201 | 1059 | 1060 | ||
202 | 1060 | 1061 | ||
203 | === modified file 'src/main/java/com/persistit/CLI.java' | |||
204 | --- src/main/java/com/persistit/CLI.java 2012-06-14 20:15:48 +0000 | |||
205 | +++ src/main/java/com/persistit/CLI.java 2012-06-29 21:06:19 +0000 | |||
206 | @@ -1021,6 +1021,9 @@ | |||
207 | 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, |
208 | 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, |
209 | 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, |
210 | 1024 | final @Arg("maxkey|int:42:4:10000|Maximum displayed key length") int maxkey, | ||
211 | 1025 | final @Arg("maxvalue|int:42:4:100000|Maximum displayed value length") int maxvalue, | ||
212 | 1026 | final @Arg("context|int:3:0:100000|Context lines") int context, | ||
213 | 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) |
214 | 1025 | throws Exception { | 1028 | throws Exception { |
215 | 1026 | 1029 | ||
216 | @@ -1084,21 +1087,8 @@ | |||
217 | 1084 | if (summary) { | 1087 | if (summary) { |
218 | 1085 | postMessage(buffer.toString(), LOG_NORMAL); | 1088 | postMessage(buffer.toString(), LOG_NORMAL); |
219 | 1086 | return; | 1089 | return; |
220 | 1087 | } | ||
221 | 1088 | String detail = buffer.toStringDetail(findPointer); | ||
222 | 1089 | if (allLines) { | ||
223 | 1090 | postMessage(detail, LOG_NORMAL); | ||
224 | 1091 | return; | ||
225 | 1092 | } else { | 1090 | } else { |
235 | 1093 | int p = -1; | 1091 | postMessage(buffer.toStringDetail(findPointer, maxkey, maxvalue, context, allLines), LOG_NORMAL); |
227 | 1094 | for (int i = 0; i < 20; i++) { | ||
228 | 1095 | p = detail.indexOf('\n', p + 1); | ||
229 | 1096 | if (p == -1) { | ||
230 | 1097 | p = detail.length(); | ||
231 | 1098 | break; | ||
232 | 1099 | } | ||
233 | 1100 | } | ||
234 | 1101 | postMessage(detail.substring(0, p), LOG_NORMAL); | ||
236 | 1102 | return; | 1092 | return; |
237 | 1103 | } | 1093 | } |
238 | 1104 | } | 1094 | } |
239 | @@ -1108,6 +1098,44 @@ | |||
240 | 1108 | } | 1098 | } |
241 | 1109 | }; | 1099 | }; |
242 | 1110 | } | 1100 | } |
243 | 1101 | |||
244 | 1102 | @Cmd("pviewchain") | ||
245 | 1103 | Task pviewchain(final @Arg("page|long:0:0:99999999999999999|Starting page address") long pageAddress, | ||
246 | 1104 | final @Arg("find|long:-1:0:99999999999999999|Optional page pointer to find") long findPointer, | ||
247 | 1105 | final @Arg("count|int:32:1:1000000|Maximum number of pages to display") long maxcount, | ||
248 | 1106 | final @Arg("maxkey|int:42:4:10000|Maximum displayed key length") int maxkey, | ||
249 | 1107 | final @Arg("maxvalue|int:42:4:100000|Maximum displayed value length") int maxvalue, | ||
250 | 1108 | final @Arg("context|int:3:0:100000|Context lines") int context, | ||
251 | 1109 | final @Arg("_flag|a|All lines") boolean allLines, final @Arg("_flag|s|Summary only") boolean summary) { | ||
252 | 1110 | |||
253 | 1111 | return new Task() { | ||
254 | 1112 | |||
255 | 1113 | @Override | ||
256 | 1114 | protected void runTask() throws Exception { | ||
257 | 1115 | long currentPage = pageAddress; | ||
258 | 1116 | int count = 0; | ||
259 | 1117 | while (currentPage > 0 && count++ < maxcount) { | ||
260 | 1118 | if (_currentVolume == null) { | ||
261 | 1119 | postMessage("Select a volume", LOG_NORMAL); | ||
262 | 1120 | return; | ||
263 | 1121 | } | ||
264 | 1122 | final Buffer buffer = _currentVolume.getPool().getBufferCopy(_currentVolume, currentPage); | ||
265 | 1123 | if (summary) { | ||
266 | 1124 | postMessage(buffer.toString(), LOG_NORMAL); | ||
267 | 1125 | } else { | ||
268 | 1126 | postMessage(buffer.toStringDetail(findPointer, maxkey, maxvalue, context, allLines), LOG_NORMAL); | ||
269 | 1127 | } | ||
270 | 1128 | currentPage = buffer.getRightSibling(); | ||
271 | 1129 | } | ||
272 | 1130 | } | ||
273 | 1131 | |||
274 | 1132 | @Override | ||
275 | 1133 | public String getStatus() { | ||
276 | 1134 | return ""; | ||
277 | 1135 | } | ||
278 | 1136 | |||
279 | 1137 | }; | ||
280 | 1138 | } | ||
281 | 1111 | 1139 | ||
282 | 1112 | @Cmd("jquery") | 1140 | @Cmd("jquery") |
283 | 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, |
284 | 1114 | 1142 | ||
285 | === modified file 'src/main/java/com/persistit/Exchange.java' | |||
286 | --- src/main/java/com/persistit/Exchange.java 2012-06-18 16:35:52 +0000 | |||
287 | +++ src/main/java/com/persistit/Exchange.java 2012-06-29 21:06:19 +0000 | |||
288 | @@ -38,7 +38,7 @@ | |||
289 | 38 | import static com.persistit.Key.LTEQ; | 38 | import static com.persistit.Key.LTEQ; |
290 | 39 | import static com.persistit.Key.RIGHT_GUARD_KEY; | 39 | import static com.persistit.Key.RIGHT_GUARD_KEY; |
291 | 40 | import static com.persistit.Key.maxStorableKeySize; | 40 | import static com.persistit.Key.maxStorableKeySize; |
293 | 41 | import static com.persistit.util.SequencerConstants.WRITE_WRITE_STORE_A; | 41 | import static com.persistit.util.SequencerConstants.*; |
294 | 42 | import static com.persistit.util.ThreadSequencer.sequence; | 42 | import static com.persistit.util.ThreadSequencer.sequence; |
295 | 43 | 43 | ||
296 | 44 | import java.util.ArrayList; | 44 | import java.util.ArrayList; |
297 | @@ -526,8 +526,7 @@ | |||
298 | 526 | int _flags; | 526 | int _flags; |
299 | 527 | long _deallocLeftPage; | 527 | long _deallocLeftPage; |
300 | 528 | long _deallocRightPage; | 528 | long _deallocRightPage; |
303 | 529 | long _deferredReindexPage; | 529 | |
302 | 530 | long _deferredReindexChangeCount; | ||
304 | 531 | 530 | ||
305 | 532 | private LevelCache(int level) { | 531 | private LevelCache(int level) { |
306 | 533 | _level = level; | 532 | _level = level; |
307 | @@ -1330,7 +1329,8 @@ | |||
308 | 1330 | 1329 | ||
309 | 1331 | final int maxSimpleValueSize = maxValueSize(key.getEncodedSize()); | 1330 | final int maxSimpleValueSize = maxValueSize(key.getEncodedSize()); |
310 | 1332 | final Value spareValue = _persistit.getThreadLocalValue(); | 1331 | final Value spareValue = _persistit.getThreadLocalValue(); |
312 | 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: " |
313 | 1333 | + value; | ||
314 | 1334 | 1334 | ||
315 | 1335 | // | 1335 | // |
316 | 1336 | // First insert the record in the data page | 1336 | // First insert the record in the data page |
317 | @@ -1356,7 +1356,7 @@ | |||
318 | 1356 | // This method may delay significantly for I/O and must | 1356 | // This method may delay significantly for I/O and must |
319 | 1357 | // be called when there are no other claimed resources. | 1357 | // be called when there are no other claimed resources. |
320 | 1358 | // | 1358 | // |
322 | 1359 | newLongRecordPointer = getLongRecordHelper().storeLongRecord(value, _transaction.isActive()); | 1359 | newLongRecordPointer = getLongRecordHelper().storeLongRecord(value, _transaction.isActive()); |
323 | 1360 | } | 1360 | } |
324 | 1361 | 1361 | ||
325 | 1362 | if (!_ignoreTransactions && ((options & StoreOptions.DONT_JOURNAL) == 0)) { | 1362 | if (!_ignoreTransactions && ((options & StoreOptions.DONT_JOURNAL) == 0)) { |
326 | @@ -1408,7 +1408,7 @@ | |||
327 | 1408 | if (!treeClaimAcquired || !_treeHolder.upgradeClaim()) { | 1408 | if (!treeClaimAcquired || !_treeHolder.upgradeClaim()) { |
328 | 1409 | treeClaimRequired = true; | 1409 | treeClaimRequired = true; |
329 | 1410 | treeWriterClaimRequired = true; | 1410 | treeWriterClaimRequired = true; |
331 | 1411 | throw new RetryException(); | 1411 | throw RetryException.SINGLE; |
332 | 1412 | } | 1412 | } |
333 | 1413 | 1413 | ||
334 | 1414 | Debug.$assert0.t(valueToStore.getPointerValue() > 0); | 1414 | Debug.$assert0.t(valueToStore.getPointerValue() > 0); |
335 | @@ -1460,9 +1460,8 @@ | |||
336 | 1460 | } | 1460 | } |
337 | 1461 | /* | 1461 | /* |
338 | 1462 | * If it was a long MVV we saved it into the | 1462 | * If it was a long MVV we saved it into the |
342 | 1463 | * variable above. Otherwise it is a | 1463 | * variable above. Otherwise it is a primordial |
343 | 1464 | * primordial value that we can't get rid | 1464 | * value that we can't get rid of. |
341 | 1465 | * of. | ||
344 | 1466 | */ | 1465 | */ |
345 | 1467 | oldLongRecordPointer = 0; | 1466 | oldLongRecordPointer = 0; |
346 | 1468 | 1467 | ||
347 | @@ -1521,7 +1520,8 @@ | |||
348 | 1521 | spareValue.setEncodedSize(storedLength); | 1520 | spareValue.setEncodedSize(storedLength); |
349 | 1522 | 1521 | ||
350 | 1523 | if (spareValue.getEncodedSize() > maxSimpleValueSize) { | 1522 | if (spareValue.getEncodedSize() > maxSimpleValueSize) { |
352 | 1524 | newLongRecordPointerMVV = getLongRecordHelper().storeLongRecord(spareValue, _transaction.isActive()); | 1523 | newLongRecordPointerMVV = getLongRecordHelper().storeLongRecord(spareValue, |
353 | 1524 | _transaction.isActive()); | ||
354 | 1525 | } | 1525 | } |
355 | 1526 | } | 1526 | } |
356 | 1527 | } | 1527 | } |
357 | @@ -1797,13 +1797,15 @@ | |||
358 | 1797 | // level cache for this level will become | 1797 | // level cache for this level will become |
359 | 1798 | // (appropriately) invalid. | 1798 | // (appropriately) invalid. |
360 | 1799 | // | 1799 | // |
361 | 1800 | |||
362 | 1801 | |||
363 | 1800 | int at = buffer.split(rightSibling, key, valueWriter, foundAt, _spareKey1, sequence, _splitPolicy); | 1802 | int at = buffer.split(rightSibling, key, valueWriter, foundAt, _spareKey1, sequence, _splitPolicy); |
364 | 1801 | if (at < 0) { | 1803 | if (at < 0) { |
365 | 1802 | lc.updateInsert(rightSibling, key, -at); | 1804 | lc.updateInsert(rightSibling, key, -at); |
366 | 1803 | } else { | 1805 | } else { |
367 | 1804 | lc.updateInsert(buffer, key, at); | 1806 | lc.updateInsert(buffer, key, at); |
368 | 1805 | } | 1807 | } |
370 | 1806 | 1808 | ||
371 | 1807 | long oldRightSibling = buffer.getRightSibling(); | 1809 | long oldRightSibling = buffer.getRightSibling(); |
372 | 1808 | long newRightSibling = rightSibling.getPageAddress(); | 1810 | long newRightSibling = rightSibling.getPageAddress(); |
373 | 1809 | 1811 | ||
374 | @@ -2148,7 +2150,8 @@ | |||
375 | 2148 | if (matches) { | 2150 | if (matches) { |
376 | 2149 | index = _key.nextElementIndex(parentIndex); | 2151 | index = _key.nextElementIndex(parentIndex); |
377 | 2150 | if (index > 0) { | 2152 | if (index > 0) { |
379 | 2151 | boolean isVisibleMatch = fetchFromBufferInternal(buffer, outValue, foundAt, minimumBytes); | 2153 | boolean isVisibleMatch = fetchFromBufferInternal(buffer, outValue, foundAt, |
380 | 2154 | minimumBytes); | ||
381 | 2152 | // | 2155 | // |
382 | 2153 | // In any case (matching sibling, child or | 2156 | // In any case (matching sibling, child or |
383 | 2154 | // niece/nephew) we need to ignore this | 2157 | // niece/nephew) we need to ignore this |
384 | @@ -2736,7 +2739,8 @@ | |||
385 | 2736 | * As thrown from any internal method. | 2739 | * As thrown from any internal method. |
386 | 2737 | * @return <code>true</code> if the value was visible. | 2740 | * @return <code>true</code> if the value was visible. |
387 | 2738 | */ | 2741 | */ |
389 | 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) |
390 | 2743 | throws PersistitException { | ||
391 | 2740 | buffer.fetch(foundAt, value); | 2744 | buffer.fetch(foundAt, value); |
392 | 2741 | return fetchFromValueInternal(value, minimumBytes, buffer); | 2745 | return fetchFromValueInternal(value, minimumBytes, buffer); |
393 | 2742 | } | 2746 | } |
394 | @@ -2744,20 +2748,21 @@ | |||
395 | 2744 | /** | 2748 | /** |
396 | 2745 | * Helper for finalizing the value to return from a, potentially, MVV | 2749 | * Helper for finalizing the value to return from a, potentially, MVV |
397 | 2746 | * contained in the given Value. | 2750 | * contained in the given Value. |
399 | 2747 | * | 2751 | * |
400 | 2748 | * @param value | 2752 | * @param value |
401 | 2749 | * Value to finalize. | 2753 | * Value to finalize. |
402 | 2750 | * @param minimumBytes | 2754 | * @param minimumBytes |
403 | 2751 | * Minimum amount of LONG_RECORD to fetch. If <0, the | 2755 | * Minimum amount of LONG_RECORD to fetch. If <0, the |
404 | 2752 | * <code>value</code> will contain just the descriptor portion. | 2756 | * <code>value</code> will contain just the descriptor portion. |
405 | 2753 | * @param bufferForPruning | 2757 | * @param bufferForPruning |
408 | 2754 | * If not <code>null</code> and <code>Value</code> did contain | 2758 | * If not <code>null</code> and <code>Value</code> did contain an |
409 | 2755 | * an MVV, call {@link Buffer#enqueuePruningAction(int)}. | 2759 | * MVV, call {@link Buffer#enqueuePruningAction(int)}. |
410 | 2756 | * @throws PersistitException | 2760 | * @throws PersistitException |
411 | 2757 | * As thrown from any internal method. | 2761 | * As thrown from any internal method. |
412 | 2758 | * @return <code>true</code> if the value was visible. | 2762 | * @return <code>true</code> if the value was visible. |
413 | 2759 | */ | 2763 | */ |
415 | 2760 | private boolean fetchFromValueInternal(Value value, int minimumBytes, Buffer bufferForPruning) throws PersistitException { | 2764 | private boolean fetchFromValueInternal(Value value, int minimumBytes, Buffer bufferForPruning) |
416 | 2765 | throws PersistitException { | ||
417 | 2761 | boolean visible = true; | 2766 | boolean visible = true; |
418 | 2762 | /* | 2767 | /* |
419 | 2763 | * We must fetch the full LONG_RECORD, if needed, while buffer is | 2768 | * We must fetch the full LONG_RECORD, if needed, while buffer is |
420 | @@ -3042,7 +3047,7 @@ | |||
421 | 3042 | 3047 | ||
422 | 3043 | /** | 3048 | /** |
423 | 3044 | * Removes all records with keys falling between <code>key1</code> and | 3049 | * Removes all records with keys falling between <code>key1</code> and |
425 | 3045 | * </code>key2</code>, lefty-inclusive. Validity checks and Key value | 3050 | * </code>key2</code>, left-inclusive. Validity checks and Key value |
426 | 3046 | * adjustments have been done by caller - this method does the work. | 3051 | * adjustments have been done by caller - this method does the work. |
427 | 3047 | * | 3052 | * |
428 | 3048 | * @param key1 | 3053 | * @param key1 |
429 | @@ -3145,7 +3150,6 @@ | |||
430 | 3145 | boolean result = false; | 3150 | boolean result = false; |
431 | 3146 | 3151 | ||
432 | 3147 | boolean deallocationRequired = true; // assume until proven false | 3152 | boolean deallocationRequired = true; // assume until proven false |
433 | 3148 | boolean deferredReindexRequired = false; | ||
434 | 3149 | boolean tryQuickDelete = true; | 3153 | boolean tryQuickDelete = true; |
435 | 3150 | 3154 | ||
436 | 3151 | if (!_ignoreTransactions) { | 3155 | if (!_ignoreTransactions) { |
437 | @@ -3227,9 +3231,6 @@ | |||
438 | 3227 | + " failed to get writer claim on " + _tree); | 3231 | + " failed to get writer claim on " + _tree); |
439 | 3228 | } | 3232 | } |
440 | 3229 | treeClaimAcquired = true; | 3233 | treeClaimAcquired = true; |
441 | 3230 | _tree.bumpGeneration(); | ||
442 | 3231 | // Because we actually haven't changed anything yet. | ||
443 | 3232 | _cachedTreeGeneration++; | ||
444 | 3233 | } | 3234 | } |
445 | 3234 | // | 3235 | // |
446 | 3235 | // Need to redo this check now that we have a | 3236 | // Need to redo this check now that we have a |
447 | @@ -3265,12 +3266,6 @@ | |||
448 | 3265 | lc._rightBuffer = buffer; | 3266 | lc._rightBuffer = buffer; |
449 | 3266 | lc._rightFoundAt = foundAt2; | 3267 | lc._rightFoundAt = foundAt2; |
450 | 3267 | } else { | 3268 | } else { |
451 | 3268 | // | ||
452 | 3269 | // Since we are spanning pages we need an | ||
453 | 3270 | // exclusive claim on the tree to prevent | ||
454 | 3271 | // an insertion from propagating upward through | ||
455 | 3272 | // the deletion range. | ||
456 | 3273 | // | ||
457 | 3274 | pageAddr2 = buffer.getRightSibling(); | 3269 | pageAddr2 = buffer.getRightSibling(); |
458 | 3275 | samePage = false; | 3270 | samePage = false; |
459 | 3276 | } | 3271 | } |
460 | @@ -3358,11 +3353,13 @@ | |||
461 | 3358 | _volume.getStructure().harvestLongRecords(buffer1, foundAt1, Integer.MAX_VALUE); | 3353 | _volume.getStructure().harvestLongRecords(buffer1, foundAt1, Integer.MAX_VALUE); |
462 | 3359 | _volume.getStructure().harvestLongRecords(buffer2, 0, foundAt2); | 3354 | _volume.getStructure().harvestLongRecords(buffer2, 0, foundAt2); |
463 | 3360 | 3355 | ||
464 | 3356 | Debug.$assert0.t(_tree.isMine() && buffer1.isMine() && buffer2.isMine()); | ||
465 | 3361 | boolean rebalanced = buffer1.join(buffer2, foundAt1, foundAt2, _spareKey1, _spareKey2, | 3357 | boolean rebalanced = buffer1.join(buffer2, foundAt1, foundAt2, _spareKey1, _spareKey2, |
466 | 3362 | _joinPolicy); | 3358 | _joinPolicy); |
467 | 3363 | if (buffer1.isDataPage()) { | 3359 | if (buffer1.isDataPage()) { |
468 | 3364 | _tree.bumpChangeCount(); | 3360 | _tree.bumpChangeCount(); |
469 | 3365 | } | 3361 | } |
470 | 3362 | |||
471 | 3366 | buffer1.setDirtyAtTimestamp(timestamp); | 3363 | buffer1.setDirtyAtTimestamp(timestamp); |
472 | 3367 | buffer2.setDirtyAtTimestamp(timestamp); | 3364 | buffer2.setDirtyAtTimestamp(timestamp); |
473 | 3368 | 3365 | ||
474 | @@ -3419,9 +3416,11 @@ | |||
475 | 3419 | } | 3416 | } |
476 | 3420 | } | 3417 | } |
477 | 3421 | if (needsReindex) { | 3418 | if (needsReindex) { |
481 | 3422 | lc._deferredReindexPage = buffer2.getPageAddress(); | 3419 | _spareKey1.copyTo(_spareKey2); |
482 | 3423 | lc._deferredReindexChangeCount = buffer2.getGeneration(); | 3420 | _value.setPointerValue(buffer2.getPageAddress()); |
483 | 3424 | deferredReindexRequired = true; | 3421 | _value.setPointerPageType(buffer2.getPageType()); |
484 | 3422 | |||
485 | 3423 | storeInternal(_spareKey2, _value, level + 1, StoreOptions.NONE); | ||
486 | 3425 | needsReindex = false; | 3424 | needsReindex = false; |
487 | 3426 | } | 3425 | } |
488 | 3427 | } | 3426 | } |
489 | @@ -3449,7 +3448,7 @@ | |||
490 | 3449 | } | 3448 | } |
491 | 3450 | break; | 3449 | break; |
492 | 3451 | } catch (RetryException re) { | 3450 | } catch (RetryException re) { |
494 | 3452 | // handled below | 3451 | // handled below by releasing claims and retrying |
495 | 3453 | } finally { | 3452 | } finally { |
496 | 3454 | // | 3453 | // |
497 | 3455 | // Release all buffers. | 3454 | // Release all buffers. |
498 | @@ -3459,10 +3458,17 @@ | |||
499 | 3459 | } | 3458 | } |
500 | 3460 | 3459 | ||
501 | 3461 | if (treeClaimAcquired) { | 3460 | if (treeClaimAcquired) { |
502 | 3461 | if (treeWriterClaimRequired) { | ||
503 | 3462 | _tree.bumpGeneration(); | ||
504 | 3463 | } | ||
505 | 3462 | _treeHolder.release(); | 3464 | _treeHolder.release(); |
506 | 3463 | treeClaimAcquired = false; | 3465 | treeClaimAcquired = false; |
507 | 3464 | } | 3466 | } |
508 | 3465 | } | 3467 | } |
509 | 3468 | /* | ||
510 | 3469 | * Having released all prior claims, now acquire an exclusive | ||
511 | 3470 | * claim on the Tree. | ||
512 | 3471 | */ | ||
513 | 3466 | if (treeWriterClaimRequired) { | 3472 | if (treeWriterClaimRequired) { |
514 | 3467 | if (!_treeHolder.claim(true)) { | 3473 | if (!_treeHolder.claim(true)) { |
515 | 3468 | Debug.$assert0.t(false); | 3474 | Debug.$assert0.t(false); |
516 | @@ -3490,51 +3496,11 @@ | |||
517 | 3490 | deallocationRequired = false; | 3496 | deallocationRequired = false; |
518 | 3491 | break; | 3497 | break; |
519 | 3492 | } | 3498 | } |
520 | 3493 | while (deferredReindexRequired) { | ||
521 | 3494 | Buffer buffer = null; | ||
522 | 3495 | try { | ||
523 | 3496 | for (int level = _cacheDepth; --level >= 0;) { | ||
524 | 3497 | LevelCache lc = _levelCache[level]; | ||
525 | 3498 | if (lc._deferredReindexPage != 0) { | ||
526 | 3499 | if (!treeClaimAcquired) { | ||
527 | 3500 | if (!_treeHolder.claim(treeWriterClaimRequired)) { | ||
528 | 3501 | Debug.$assert0.t(false); | ||
529 | 3502 | throw new InUseException("Thread " + Thread.currentThread().getName() | ||
530 | 3503 | + " failed to get writer claim on " + _tree); | ||
531 | 3504 | } | ||
532 | 3505 | treeClaimAcquired = true; | ||
533 | 3506 | } | ||
534 | 3507 | |||
535 | 3508 | long deferredPage = lc._deferredReindexPage; | ||
536 | 3509 | buffer = _pool.get(_volume, deferredPage, false, true); | ||
537 | 3510 | if (buffer.getGeneration() == lc._deferredReindexChangeCount) { | ||
538 | 3511 | checkPageType(buffer, level + PAGE_TYPE_DATA, false); | ||
539 | 3512 | buffer.nextKey(_spareKey2, buffer.toKeyBlock(0)); | ||
540 | 3513 | _value.setPointerValue(buffer.getPageAddress()); | ||
541 | 3514 | _value.setPointerPageType(buffer.getPageType()); | ||
542 | 3515 | storeInternal(_spareKey2, _value, level + 1, StoreOptions.NONE); | ||
543 | 3516 | } else { | ||
544 | 3517 | _persistit.getLogBase().unindexedPage.log(deferredPage, _volume, _tree.getName()); | ||
545 | 3518 | } | ||
546 | 3519 | lc._deferredReindexPage = 0; | ||
547 | 3520 | buffer.releaseTouched(); | ||
548 | 3521 | buffer = null; | ||
549 | 3522 | } | ||
550 | 3523 | } | ||
551 | 3524 | deferredReindexRequired = false; | ||
552 | 3525 | } catch (RetryException re) { | ||
553 | 3526 | // can this even be thrown here? | ||
554 | 3527 | } finally { | ||
555 | 3528 | if (buffer != null) { | ||
556 | 3529 | buffer.releaseTouched(); | ||
557 | 3530 | buffer = null; | ||
558 | 3531 | } | ||
559 | 3532 | } | ||
560 | 3533 | } | ||
561 | 3534 | |||
562 | 3535 | } finally { | 3499 | } finally { |
563 | 3536 | if (treeClaimAcquired) { | 3500 | if (treeClaimAcquired) { |
565 | 3537 | _tree.bumpGeneration(); | 3501 | if (treeWriterClaimRequired) { |
566 | 3502 | _tree.bumpGeneration(); | ||
567 | 3503 | } | ||
568 | 3538 | _treeHolder.release(); | 3504 | _treeHolder.release(); |
569 | 3539 | treeClaimAcquired = false; | 3505 | treeClaimAcquired = false; |
570 | 3540 | } | 3506 | } |
571 | 3541 | 3507 | ||
572 | === modified file 'src/main/java/com/persistit/IntegrityCheck.java' | |||
573 | --- src/main/java/com/persistit/IntegrityCheck.java 2012-06-26 19:36:26 +0000 | |||
574 | +++ src/main/java/com/persistit/IntegrityCheck.java 2012-06-29 21:06:19 +0000 | |||
575 | @@ -339,7 +339,14 @@ | |||
576 | 339 | } | 339 | } |
577 | 340 | 340 | ||
578 | 341 | private void addFault(String description, long page, int level, int position) { | 341 | private void addFault(String description, long page, int level, int position) { |
580 | 342 | Fault fault = new Fault(resourceName(), this, description, page, level, position); | 342 | Fault fault = new Fault(resourceName(), this, description, page, _treeDepth, level, position); |
581 | 343 | if (_faults.size() < MAX_FAULTS) | ||
582 | 344 | _faults.add(fault); | ||
583 | 345 | postMessage(fault.toString(), LOG_VERBOSE); | ||
584 | 346 | } | ||
585 | 347 | |||
586 | 348 | private void addGarbageFault(String description, long page, int level, int position) { | ||
587 | 349 | Fault fault = new Fault(resourceName(), this, description, page, 3, level, position); | ||
588 | 343 | if (_faults.size() < MAX_FAULTS) | 350 | if (_faults.size() < MAX_FAULTS) |
589 | 344 | _faults.add(fault); | 351 | _faults.add(fault); |
590 | 345 | postMessage(fault.toString(), LOG_VERBOSE); | 352 | postMessage(fault.toString(), LOG_VERBOSE); |
591 | @@ -645,10 +652,10 @@ | |||
592 | 645 | int _depth; | 652 | int _depth; |
593 | 646 | int _position; | 653 | int _position; |
594 | 647 | 654 | ||
596 | 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) { |
597 | 649 | _treeName = treeName; | 656 | _treeName = treeName; |
598 | 650 | _description = description; | 657 | _description = description; |
600 | 651 | _depth = work._treeDepth; | 658 | _depth = depth; |
601 | 652 | _path = new long[_depth - level]; | 659 | _path = new long[_depth - level]; |
602 | 653 | for (int index = _depth; --index > level;) { | 660 | for (int index = _depth; --index > level;) { |
603 | 654 | if (index >= work._edgeBuffers.length) { | 661 | if (index >= work._edgeBuffers.length) { |
604 | @@ -657,7 +664,7 @@ | |||
605 | 657 | _path[index - level] = work._edgePages[index]; | 664 | _path[index - level] = work._edgePages[index]; |
606 | 658 | } | 665 | } |
607 | 659 | } | 666 | } |
609 | 660 | _path[level] = page; | 667 | _path[0] = page; |
610 | 661 | _level = level; | 668 | _level = level; |
611 | 662 | _depth = work._treeDepth; | 669 | _depth = work._treeDepth; |
612 | 663 | _position = position; | 670 | _position = position; |
613 | @@ -994,11 +1001,11 @@ | |||
614 | 994 | private void checkGarbagePage(Buffer garbageBuffer) throws PersistitException { | 1001 | private void checkGarbagePage(Buffer garbageBuffer) throws PersistitException { |
615 | 995 | long page = garbageBuffer.getPageAddress(); | 1002 | long page = garbageBuffer.getPageAddress(); |
616 | 996 | if (!garbageBuffer.isGarbagePage()) { | 1003 | if (!garbageBuffer.isGarbagePage()) { |
618 | 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); |
619 | 998 | return; | 1005 | return; |
620 | 999 | } | 1006 | } |
621 | 1000 | if (_usedPageBits.get(page)) { | 1007 | if (_usedPageBits.get(page)) { |
623 | 1001 | addFault("Garbage page is referenced by multiple parents", page, 1, 0); | 1008 | addGarbageFault("Garbage page is referenced by multiple parents", page, 1, 0); |
624 | 1002 | return; | 1009 | return; |
625 | 1003 | } | 1010 | } |
626 | 1004 | 1011 | ||
627 | @@ -1007,7 +1014,7 @@ | |||
628 | 1007 | int size = garbageBuffer.getBufferSize(); | 1014 | int size = garbageBuffer.getBufferSize(); |
629 | 1008 | int count = (size - next) / Buffer.GARBAGE_BLOCK_SIZE; | 1015 | int count = (size - next) / Buffer.GARBAGE_BLOCK_SIZE; |
630 | 1009 | if (count * Buffer.GARBAGE_BLOCK_SIZE != (size - next)) { | 1016 | if (count * Buffer.GARBAGE_BLOCK_SIZE != (size - next)) { |
632 | 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 " |
633 | 1011 | + Buffer.GARBAGE_BLOCK_SIZE + " bytes", page, 1, 0); | 1018 | + Buffer.GARBAGE_BLOCK_SIZE + " bytes", page, 1, 0); |
634 | 1012 | } | 1019 | } |
635 | 1013 | _usedPageBits.set(page, true); | 1020 | _usedPageBits.set(page, true); |
636 | @@ -1026,12 +1033,12 @@ | |||
637 | 1026 | _edgePages[2] = page; | 1033 | _edgePages[2] = page; |
638 | 1027 | while (page != 0 && page != right) { | 1034 | while (page != 0 && page != right) { |
639 | 1028 | if (_usedPageBits.get(page)) { | 1035 | if (_usedPageBits.get(page)) { |
641 | 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); |
642 | 1030 | return; | 1037 | return; |
643 | 1031 | } | 1038 | } |
644 | 1032 | Buffer buffer = getPage(page); | 1039 | Buffer buffer = getPage(page); |
645 | 1033 | if (!buffer.isDataPage() && !buffer.isIndexPage() && !buffer.isLongRecordPage()) { | 1040 | if (!buffer.isDataPage() && !buffer.isIndexPage() && !buffer.isLongRecordPage()) { |
647 | 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); |
648 | 1035 | } | 1042 | } |
649 | 1036 | _counters._garbagePageCount++; | 1043 | _counters._garbagePageCount++; |
650 | 1037 | _pagesVisited++; | 1044 | _pagesVisited++; |
651 | 1038 | 1045 | ||
652 | === modified file 'src/main/java/com/persistit/VolumeStorageV2.java' | |||
653 | --- src/main/java/com/persistit/VolumeStorageV2.java 2012-06-22 19:07:13 +0000 | |||
654 | +++ src/main/java/com/persistit/VolumeStorageV2.java 2012-06-29 21:06:19 +0000 | |||
655 | @@ -363,6 +363,7 @@ | |||
656 | 363 | _headBuffer = _volume.getStructure().getPool().get(_volume, 0, true, false); | 363 | _headBuffer = _volume.getStructure().getPool().get(_volume, 0, true, false); |
657 | 364 | boolean truncated = false; | 364 | boolean truncated = false; |
658 | 365 | try { | 365 | try { |
659 | 366 | _headBuffer.init(Buffer.PAGE_TYPE_HEAD); | ||
660 | 366 | _headBuffer.setFixed(); | 367 | _headBuffer.setFixed(); |
661 | 367 | 368 | ||
662 | 368 | initMetaData(_headBuffer.getBytes()); | 369 | initMetaData(_headBuffer.getBytes()); |
663 | 369 | 370 | ||
664 | === modified file 'src/main/java/com/persistit/exception/RetryException.java' | |||
665 | --- src/main/java/com/persistit/exception/RetryException.java 2012-05-25 18:50:59 +0000 | |||
666 | +++ src/main/java/com/persistit/exception/RetryException.java 2012-06-29 21:06:19 +0000 | |||
667 | @@ -30,7 +30,4 @@ | |||
668 | 30 | 30 | ||
669 | 31 | public final static RetryException SINGLE = new RetryException(); | 31 | public final static RetryException SINGLE = new RetryException(); |
670 | 32 | 32 | ||
671 | 33 | public RetryException() { | ||
672 | 34 | |||
673 | 35 | } | ||
674 | 36 | } | 33 | } |
675 | 37 | 34 | ||
676 | === modified file 'src/main/java/com/persistit/util/Debug.java' | |||
677 | --- src/main/java/com/persistit/util/Debug.java 2012-06-04 18:58:23 +0000 | |||
678 | +++ src/main/java/com/persistit/util/Debug.java 2012-06-29 21:06:19 +0000 | |||
679 | @@ -106,6 +106,32 @@ | |||
680 | 106 | } | 106 | } |
681 | 107 | System.err.println("Debug " + sb.toString()); | 107 | System.err.println("Debug " + sb.toString()); |
682 | 108 | } | 108 | } |
683 | 109 | |||
684 | 110 | public static String trace(int from, int to) { | ||
685 | 111 | return " " + Thread.currentThread().getName() + " {" + Debug.callStack(from + 2, to + 2) + "}"; | ||
686 | 112 | } | ||
687 | 113 | |||
688 | 114 | public static String callStack(final int from, final int to) { | ||
689 | 115 | RuntimeException exception = new RuntimeException(); | ||
690 | 116 | exception.fillInStackTrace(); | ||
691 | 117 | StackTraceElement[] elements = exception.getStackTrace(); | ||
692 | 118 | int a = Math.max(0, from); | ||
693 | 119 | int b = Math.min(to, elements.length); | ||
694 | 120 | StringBuilder sb = new StringBuilder(); | ||
695 | 121 | for (int index = b; index >= a; index--) { | ||
696 | 122 | StackTraceElement t = exception.getStackTrace()[index]; | ||
697 | 123 | if (index != b) { | ||
698 | 124 | sb.append("->"); | ||
699 | 125 | } | ||
700 | 126 | sb.append(t.getClassName()); | ||
701 | 127 | sb.append('#'); | ||
702 | 128 | sb.append(t.getMethodName()); | ||
703 | 129 | sb.append('['); | ||
704 | 130 | sb.append(t.getLineNumber()); | ||
705 | 131 | sb.append("]"); | ||
706 | 132 | } | ||
707 | 133 | return sb.toString(); | ||
708 | 134 | } | ||
709 | 109 | 135 | ||
710 | 110 | /** | 136 | /** |
711 | 111 | * Set the suspend flag so that callers to the suspend method either do or | 137 | * Set the suspend flag so that callers to the suspend method either do or |
712 | 112 | 138 | ||
713 | === modified file 'src/main/java/com/persistit/util/SequencerConstants.java' | |||
714 | --- src/main/java/com/persistit/util/SequencerConstants.java 2012-05-25 18:50:59 +0000 | |||
715 | +++ src/main/java/com/persistit/util/SequencerConstants.java 2012-06-29 21:06:19 +0000 | |||
716 | @@ -97,4 +97,5 @@ | |||
717 | 97 | int LONG_RECORD_ALLOCATE_A = allocate("LONG_RECORD_ALLOCATE_A"); | 97 | int LONG_RECORD_ALLOCATE_A = allocate("LONG_RECORD_ALLOCATE_A"); |
718 | 98 | int LONG_RECORD_ALLOCATE_B = allocate("LONG_RECORD_ALLOCATE_B"); | 98 | int LONG_RECORD_ALLOCATE_B = allocate("LONG_RECORD_ALLOCATE_B"); |
719 | 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)}; |
720 | 100 | |||
721 | 100 | } | 101 | } |
722 | 101 | 102 | ||
723 | === modified file 'src/main/java/com/persistit/util/Util.java' | |||
724 | --- src/main/java/com/persistit/util/Util.java 2012-05-25 18:50:59 +0000 | |||
725 | +++ src/main/java/com/persistit/util/Util.java 2012-06-29 21:06:19 +0000 | |||
726 | @@ -234,6 +234,14 @@ | |||
727 | 234 | return format(Long.toString(i), width, true); | 234 | return format(Long.toString(i), width, true); |
728 | 235 | } | 235 | } |
729 | 236 | 236 | ||
730 | 237 | public static String abridge(final String s, final int maxLength) { | ||
731 | 238 | if (s.length() > maxLength) { | ||
732 | 239 | return s.substring(0, maxLength - 3) + "..."; | ||
733 | 240 | } else { | ||
734 | 241 | return s; | ||
735 | 242 | } | ||
736 | 243 | } | ||
737 | 244 | |||
738 | 237 | public static boolean equalsByteSubarray(byte[] source, int next, byte[] target) { | 245 | public static boolean equalsByteSubarray(byte[] source, int next, byte[] target) { |
739 | 238 | return equalsByteSubarray(source, next, target, 0, target.length); | 246 | return equalsByteSubarray(source, next, target, 0, target.length); |
740 | 239 | } | 247 | } |
741 | 240 | 248 | ||
742 | === added file 'src/test/java/com/persistit/Bug1017957Test.java' | |||
743 | --- src/test/java/com/persistit/Bug1017957Test.java 1970-01-01 00:00:00 +0000 | |||
744 | +++ src/test/java/com/persistit/Bug1017957Test.java 2012-06-29 21:06:19 +0000 | |||
745 | @@ -0,0 +1,235 @@ | |||
746 | 1 | /** | ||
747 | 2 | * Copyright © 2012 Akiban Technologies, Inc. All rights reserved. | ||
748 | 3 | * | ||
749 | 4 | * This program is free software: you can redistribute it and/or modify | ||
750 | 5 | * it under the terms of the GNU Affero General Public License as | ||
751 | 6 | * published by the Free Software Foundation, version 3 (only) of the | ||
752 | 7 | * License. | ||
753 | 8 | * | ||
754 | 9 | * This program is distributed in the hope that it will be useful, | ||
755 | 10 | * but WITHOUT ANY WARRANTY; without even the implied warranty of | ||
756 | 11 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the | ||
757 | 12 | * GNU Affero General Public License for more details. | ||
758 | 13 | * | ||
759 | 14 | * You should have received a copy of the GNU Affero General Public License | ||
760 | 15 | * along with this program. If not, see <http://www.gnu.org/licenses/>. | ||
761 | 16 | * | ||
762 | 17 | * This program may also be available under different license terms. For more | ||
763 | 18 | * information, see www.akiban.com or contact licensing@akiban.com. | ||
764 | 19 | */ | ||
765 | 20 | |||
766 | 21 | package com.persistit; | ||
767 | 22 | |||
768 | 23 | import static org.junit.Assert.assertEquals; | ||
769 | 24 | |||
770 | 25 | import java.io.PrintWriter; | ||
771 | 26 | import java.util.Properties; | ||
772 | 27 | import java.util.concurrent.atomic.AtomicInteger; | ||
773 | 28 | |||
774 | 29 | import org.junit.Test; | ||
775 | 30 | |||
776 | 31 | import com.persistit.mxbeans.ManagementMXBean; | ||
777 | 32 | import com.persistit.unit.PersistitUnitTestCase; | ||
778 | 33 | import com.persistit.unit.UnitTestProperties; | ||
779 | 34 | |||
780 | 35 | /** | ||
781 | 36 | * https://bugs.launchpad.net/akiban-persistit/+bug/1017957 | ||
782 | 37 | * | ||
783 | 38 | * During the past week the 8-hour stress test suite has generated several | ||
784 | 39 | * CorruptVolumeExceptions and other related phenomena. Examples: | ||
785 | 40 | * | ||
786 | 41 | * Stress6 [main] FAILED: com.persistit.exception.CorruptVolumeException: Volume | ||
787 | 42 | * persistit(/tmp/persistit_tests/persistit) level=0 page=15684 | ||
788 | 43 | * initialPage=57164 | ||
789 | 44 | * key=<{"stress6",98,5,"xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx"}> walked | ||
790 | 45 | * right more than 50 pages last page visited=81324 at | ||
791 | 46 | * com.persistit.Exchange.corrupt(Exchange.java:3884) at | ||
792 | 47 | * com.persistit.Exchange.searchLevel(Exchange.java:1250) at | ||
793 | 48 | * com.persistit.Exchange.searchTree(Exchange.java:1125) at | ||
794 | 49 | * com.persistit.Exchange.storeInternal(Exchange.java:1443) at | ||
795 | 50 | * com.persistit.Exchange.store(Exchange.java:1294) at | ||
796 | 51 | * com.persistit.Exchange.store(Exchange.java:2534) at | ||
797 | 52 | * com.persistit.stress.unit.Stress6.executeTest(Stress6.java:98) at | ||
798 | 53 | * com.persistit.stress.AbstractStressTest.run(AbstractStressTest.java:93) at | ||
799 | 54 | * java.lang.Thread.run(Thread.java:662) | ||
800 | 55 | * | ||
801 | 56 | * Stress2txn [main] FAILED: com.persistit.exception.RebalanceException at | ||
802 | 57 | * com.persistit.Buffer.join(Buffer.java:2523) at | ||
803 | 58 | * com.persistit.Exchange.raw_removeKeyRangeInternal(Exchange.java:3367) at | ||
804 | 59 | * com.persistit.Exchange.removeKeyRangeInternal(Exchange.java:3070) at | ||
805 | 60 | * com.persistit.Exchange.removeInternal(Exchange.java:2999) at | ||
806 | 61 | * com.persistit.Exchange.remove(Exchange.java:2927) at | ||
807 | 62 | * com.persistit.stress.unit.Stress2txn.executeTest(Stress2txn.java:231) at | ||
808 | 63 | * com.persistit.stress.AbstractStressTest.run(AbstractStressTest.java:93) at | ||
809 | 64 | * java.lang.Thread.run(Thread.java:662) | ||
810 | 65 | * | ||
811 | 66 | * Stress2txn [main] FAILED: com.persistit.exception.CorruptVolumeException: | ||
812 | 67 | * LONG_RECORD chain is invalid at page 111919 - invalid page type: Page 111,919 | ||
813 | 68 | * in volume persistit(/tmp/persistit_tests/persistit) at index 1,559 | ||
814 | 69 | * timestamp=909,787,072 status=vr1 type=Data at | ||
815 | 70 | * com.persistit.LongRecordHelper.corrupt(LongRecordHelper.java:243) at | ||
816 | 71 | * com.persistit.LongRecordHelper.fetchLongRecord(LongRecordHelper.java:103) at | ||
817 | 72 | * com.persistit.Exchange.fetchFixupForLongRecords(Exchange.java:2841) at | ||
818 | 73 | * com.persistit.Exchange.fetchFromValueInternal(Exchange.java:2778) at | ||
819 | 74 | * com.persistit.Exchange.fetchFromBufferInternal(Exchange.java:2747) at | ||
820 | 75 | * com.persistit.Exchange.traverse(Exchange.java:2157) at | ||
821 | 76 | * com.persistit.Exchange.traverse(Exchange.java:1960) at | ||
822 | 77 | * com.persistit.Exchange.traverse(Exchange.java:1897) at | ||
823 | 78 | * com.persistit.Exchange.next(Exchange.java:2330) at | ||
824 | 79 | * com.persistit.stress.unit.Stress2txn.executeTest(Stress2txn.java:188) at | ||
825 | 80 | * com.persistit.stress.AbstractStressTest.run(AbstractStressTest.java:93) at | ||
826 | 81 | * java.lang.Thread.run(Thread.java:662) | ||
827 | 82 | * | ||
828 | 83 | * Bug mechanism #1: | ||
829 | 84 | * | ||
830 | 85 | * An obscure path through Exchange#raw_removeKeyRangeInternal inserts a | ||
831 | 86 | * key-pointer pair into an index page. It does so after removing all claims on | ||
832 | 87 | * pages and the tree itself. After removing claims, before inserting the | ||
833 | 88 | * key-pointer pair we believe the page itself gets put unto a garbage chain. So | ||
834 | 89 | * after the re-insertion, the index page now has a pointer to a page that will | ||
835 | 90 | * be reused and will contain unrelated data. | ||
836 | 91 | * | ||
837 | 92 | * | ||
838 | 93 | * Bug mechanism #2: An obscure path through Exchange#raw_removeKeyRangeInternal | ||
839 | 94 | * performs a structure delete (i.e., joins one more pairs of pages) but fails | ||
840 | 95 | * to bump the Tree generation. The allows use of a stale LevelCache array. | ||
841 | 96 | * | ||
842 | 97 | * This test procedure exhibited both bug mechanisms reliably within 10 seconds | ||
843 | 98 | * prior to fixing the code. We also implemented a test method based on the | ||
844 | 99 | * ThreadSequencer to precisely elaborate the sequence of interactions between | ||
845 | 100 | * two threads that cause the failure. However, the bug fix eliminates the code | ||
846 | 101 | * path that allows the sequencer to work, so the test was removed. | ||
847 | 102 | * | ||
848 | 103 | * @author peter | ||
849 | 104 | * | ||
850 | 105 | */ | ||
851 | 106 | public class Bug1017957Test extends PersistitUnitTestCase { | ||
852 | 107 | |||
853 | 108 | @Override | ||
854 | 109 | protected Properties getProperties(boolean cleanup) { | ||
855 | 110 | return UnitTestProperties.getBiggerProperties(cleanup); | ||
856 | 111 | } | ||
857 | 112 | |||
858 | 113 | private final long STRESS_NANOS = 10L * 1000000000L; | ||
859 | 114 | |||
860 | 115 | /** | ||
861 | 116 | * | ||
862 | 117 | * @throws Exception | ||
863 | 118 | */ | ||
864 | 119 | @Test | ||
865 | 120 | public void induceCorruptionByStress() throws Exception { | ||
866 | 121 | final long expiresAt = System.nanoTime() + STRESS_NANOS; | ||
867 | 122 | final AtomicInteger totalErrors = new AtomicInteger(); | ||
868 | 123 | Thread t1 = new Thread(new Runnable() { | ||
869 | 124 | public void run() { | ||
870 | 125 | int count = 0; | ||
871 | 126 | int errors = 0; | ||
872 | 127 | try { | ||
873 | 128 | Exchange ex = _persistit.getExchange("persistit", "Bug1017957Test", true); | ||
874 | 129 | while (System.nanoTime() < expiresAt) { | ||
875 | 130 | try { | ||
876 | 131 | Key key = createUnsafeStructure(ex); | ||
877 | 132 | removeInterestingKey(ex, key); | ||
878 | 133 | if (++count % 5000 == 0) { | ||
879 | 134 | System.out.printf("T1 iterations %,d\n", count); | ||
880 | 135 | } | ||
881 | 136 | } catch (Exception e) { | ||
882 | 137 | if (++errors < 10) { | ||
883 | 138 | e.printStackTrace(); | ||
884 | 139 | } | ||
885 | 140 | totalErrors.incrementAndGet(); | ||
886 | 141 | } | ||
887 | 142 | } | ||
888 | 143 | } catch (Exception e) { | ||
889 | 144 | throw new RuntimeException(e); | ||
890 | 145 | } | ||
891 | 146 | } | ||
892 | 147 | }); | ||
893 | 148 | |||
894 | 149 | Thread t2 = new Thread(new Runnable() { | ||
895 | 150 | public void run() { | ||
896 | 151 | int count = 0; | ||
897 | 152 | int errors = 0; | ||
898 | 153 | try { | ||
899 | 154 | Exchange ex = _persistit.getExchange("persistit", "Bug1017957Test", true); | ||
900 | 155 | while (System.nanoTime() < expiresAt) { | ||
901 | 156 | try { | ||
902 | 157 | removeCoveringRange(ex); | ||
903 | 158 | insertOtherStuff(ex); | ||
904 | 159 | if (++count % 5000 == 0) { | ||
905 | 160 | System.out.printf("T2 iterations %,d\n", count); | ||
906 | 161 | } | ||
907 | 162 | } catch (Exception e) { | ||
908 | 163 | if (++errors < 10) { | ||
909 | 164 | e.printStackTrace(); | ||
910 | 165 | } | ||
911 | 166 | totalErrors.incrementAndGet(); | ||
912 | 167 | } | ||
913 | 168 | } | ||
914 | 169 | } catch (Exception e) { | ||
915 | 170 | if (++errors < 10) { | ||
916 | 171 | e.printStackTrace(); | ||
917 | 172 | } | ||
918 | 173 | } | ||
919 | 174 | } | ||
920 | 175 | }); | ||
921 | 176 | |||
922 | 177 | t1.start(); | ||
923 | 178 | t2.start(); | ||
924 | 179 | t1.join(); | ||
925 | 180 | t2.join(); | ||
926 | 181 | |||
927 | 182 | IntegrityCheck icheck = new IntegrityCheck(_persistit); | ||
928 | 183 | icheck.setMessageLogVerbosity(Task.LOG_VERBOSE); | ||
929 | 184 | icheck.setMessageWriter(new PrintWriter(System.out)); | ||
930 | 185 | icheck.checkVolume(_persistit.getVolume("persistit")); | ||
931 | 186 | System.out.printf("\nTotal errors %d", totalErrors.get()); | ||
932 | 187 | assertEquals("Corrupt volume", 0, icheck.getFaults().length); | ||
933 | 188 | assertEquals("Exception occurred", 0, totalErrors.get()); | ||
934 | 189 | } | ||
935 | 190 | |||
936 | 191 | /** | ||
937 | 192 | * Create a B-Tree with a structure that will induce a deferred index | ||
938 | 193 | * insertion on removal of key. We need an index page that's pretty full | ||
939 | 194 | * such that removing a key and inserting a different one will result in | ||
940 | 195 | * splitting the index page. | ||
941 | 196 | * | ||
942 | 197 | * @throws Exception | ||
943 | 198 | */ | ||
944 | 199 | private Key createUnsafeStructure(final Exchange ex) throws Exception { | ||
945 | 200 | Key result = null; | ||
946 | 201 | final String v = createString(5500); // less than long record | ||
947 | 202 | final String k = createString(1040); | ||
948 | 203 | for (int i = 1000; i < 1019; i++) { | ||
949 | 204 | if (i == 1009) { | ||
950 | 205 | ex.clear().append(i).append(k.substring(0, 20)); | ||
951 | 206 | ex.getValue().put("interesting"); | ||
952 | 207 | ex.store(); | ||
953 | 208 | result = new Key(ex.getKey()); | ||
954 | 209 | } | ||
955 | 210 | ex.clear().append(i).append(k); | ||
956 | 211 | ex.getValue().put(v); | ||
957 | 212 | ex.store(); | ||
958 | 213 | } | ||
959 | 214 | return result; | ||
960 | 215 | } | ||
961 | 216 | |||
962 | 217 | private void removeInterestingKey(final Exchange ex, final Key interestingKey) throws Exception { | ||
963 | 218 | interestingKey.copyTo(ex.getKey()); | ||
964 | 219 | ex.remove(); | ||
965 | 220 | } | ||
966 | 221 | |||
967 | 222 | private void removeCoveringRange(final Exchange ex) throws Exception { | ||
968 | 223 | final Key key1 = new Key(_persistit).append(1005); | ||
969 | 224 | final Key key2 = new Key(_persistit).append(1015); | ||
970 | 225 | ex.removeKeyRange(key1, key2); | ||
971 | 226 | } | ||
972 | 227 | |||
973 | 228 | private void insertOtherStuff(final Exchange ex) throws Exception { | ||
974 | 229 | for (int k = 0; k < 100; k++) { | ||
975 | 230 | ex.clear().append(1009).append(k).append(RED_FOX); | ||
976 | 231 | ex.getValue().put(RED_FOX); | ||
977 | 232 | ex.store(); | ||
978 | 233 | } | ||
979 | 234 | } | ||
980 | 235 | } | ||
981 | 0 | 236 | ||
982 | === modified file 'src/test/java/com/persistit/IntegrityCheckTest.java' | |||
983 | --- src/test/java/com/persistit/IntegrityCheckTest.java 2012-05-25 18:50:59 +0000 | |||
984 | +++ src/test/java/com/persistit/IntegrityCheckTest.java 2012-06-29 21:06:19 +0000 | |||
985 | @@ -211,6 +211,16 @@ | |||
986 | 211 | _persistit.getTransactionIndex().cleanup(); | 211 | _persistit.getTransactionIndex().cleanup(); |
987 | 212 | assertEquals(0, _persistit.getTransactionIndex().getAbortedCount()); | 212 | assertEquals(0, _persistit.getTransactionIndex().getAbortedCount()); |
988 | 213 | } | 213 | } |
989 | 214 | |||
990 | 215 | @Test | ||
991 | 216 | public void testCorruptGarbageChain() throws PersistitException { | ||
992 | 217 | final Exchange ex = _persistit.getExchange(_volumeName, "mvv", true); | ||
993 | 218 | nonTransactionalStore(ex); | ||
994 | 219 | corrupt4(ex); | ||
995 | 220 | IntegrityCheck icheck = icheck(); | ||
996 | 221 | icheck.checkVolume(ex.getVolume()); | ||
997 | 222 | assertTrue(icheck.getFaults().length > 0); | ||
998 | 223 | } | ||
999 | 214 | 224 | ||
1000 | 215 | private String key(final int i) { | 225 | private String key(final int i) { |
1001 | 216 | return String.format("%05d%s", i, RED_FOX); | 226 | return String.format("%05d%s", i, RED_FOX); |
1002 | @@ -314,6 +324,19 @@ | |||
1003 | 314 | buffer.setDirtyAtTimestamp(_persistit.getTimestampAllocator().updateTimestamp()); | 324 | buffer.setDirtyAtTimestamp(_persistit.getTimestampAllocator().updateTimestamp()); |
1004 | 315 | buffer.release(); | 325 | buffer.release(); |
1005 | 316 | } | 326 | } |
1006 | 327 | |||
1007 | 328 | /** | ||
1008 | 329 | * Corrupts garbage chain by adding a live data chain to it | ||
1009 | 330 | * @throw PersistitException | ||
1010 | 331 | */ | ||
1011 | 332 | private void corrupt4(final Exchange ex) throws PersistitException { | ||
1012 | 333 | Key key = ex.getKey(); | ||
1013 | 334 | ex.clear().to(key(500)); | ||
1014 | 335 | Buffer copy = ex.fetchBufferCopy(0); | ||
1015 | 336 | Buffer buffer = ex.getBufferPool().get(ex.getVolume(), copy.getPageAddress(), true, true); | ||
1016 | 337 | ex.getVolume().getStructure().deallocateGarbageChain(buffer.getPageAddress(), buffer.getRightSibling()); | ||
1017 | 338 | buffer.release(); | ||
1018 | 339 | } | ||
1019 | 317 | 340 | ||
1020 | 318 | private IntegrityCheck icheck() { | 341 | private IntegrityCheck icheck() { |
1021 | 319 | IntegrityCheck icheck = new IntegrityCheck(_persistit); | 342 | IntegrityCheck icheck = new IntegrityCheck(_persistit); |
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_removeKeyRa ngeInternal. 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...