Merge lp:~pbeaman/akiban-persistit/fix-1021734-nightly-deadlock into lp:akiban-persistit
- fix-1021734-nightly-deadlock
- Merge into trunk
Status: | Merged | ||||||||
---|---|---|---|---|---|---|---|---|---|
Approved by: | Nathan Williams | ||||||||
Approved revision: | 378 | ||||||||
Merged at revision: | 372 | ||||||||
Proposed branch: | lp:~pbeaman/akiban-persistit/fix-1021734-nightly-deadlock | ||||||||
Merge into: | lp:akiban-persistit | ||||||||
Diff against target: |
965 lines (+391/-134) 18 files modified
src/main/java/com/persistit/Buffer.java (+43/-11) src/main/java/com/persistit/BufferPool.java (+41/-29) src/main/java/com/persistit/CleanupManager.java (+1/-1) src/main/java/com/persistit/Exchange.java (+3/-1) src/main/java/com/persistit/IOTaskRunnable.java (+14/-5) src/main/java/com/persistit/JournalManager.java (+1/-1) src/main/java/com/persistit/MVV.java (+1/-2) src/main/java/com/persistit/SessionId.java (+9/-0) src/main/java/com/persistit/SharedResource.java (+7/-2) src/main/java/com/persistit/Tree.java (+1/-1) src/main/java/com/persistit/Value.java (+2/-2) src/main/java/com/persistit/VolumeStorageV2.java (+1/-2) src/main/java/com/persistit/VolumeStructure.java (+127/-68) src/test/java/com/persistit/FastIndexTest.java (+1/-1) src/test/java/com/persistit/MVCCPruneBufferTest.java (+27/-6) src/test/java/com/persistit/VolumeStructureTest.java (+104/-0) src/test/java/com/persistit/stress/unit/Stress2txn.java (+7/-1) src/test/java/com/persistit/stress/unit/Stress6.java (+1/-1) |
||||||||
To merge this branch: | bzr merge lp:~pbeaman/akiban-persistit/fix-1021734-nightly-deadlock | ||||||||
Related bugs: |
|
Reviewer | Review Type | Date Requested | Status |
---|---|---|---|
Akiban Build User | Needs Fixing | ||
Nathan Williams | Approve | ||
Review via email: mp+126525@code.launchpad.net |
Commit message
Description of the change
Fix 1021734 Deadlock detected in nightly stress tests, 1053680 Page deallocation can permanently lose pages and a possible database corruption issue. This branch contains fixes for problems discovered after a lengthy period of investigation. It remains to be proven that there are no other code paths capable of producing the deadlock condition, but this code fixes the case I was able to reproduce several times, and stress tests have run for a few hours without problems with the fixes.
Changes include:
When BufferPool#
Rework of the BufferPool#get(...) method in the case the desired page is found but owned by another thread. This code path was responsible for a deadlock condition because the other thread could claim the buffer and then evict the page and load another page. The normal latching pattern precludes the possibility of deadlock, but in this special case where the identity of the page in the buffer changes, the invariants no longer apply. The bug was rare because the mechanism requires the conjunction of two conditions: Thread A is getting page X while holding page Y, and Thread B has the buffer than used to hold X but has loaded some other page Z into it, and now B wants page Y.
The modification uses a short polling cycle to validate the identity of the page before reattempting the call to claim one every half-second or so.
Rework of VolumeStructure
VolumeStructure
Various new asserts have been added.
I have not attempted unit tests for these fixes yet because (a) I would like to get the changes in to trunk soonest so we can easily schedule nightly stress tests, and (b) it's quite difficult to set up the preconditions for any of the failures involved.
Peter Beaman (pbeaman) wrote : | # |
Thanks for the review.
Buffer#
Buffer#
Renamed isMine() and isOther() to isOwnedAsWriter
Added text/documentation to several asserts and did some further cleanup. For example there was the
assert rightPage != -1
which was actually superfluous.
Nathan Williams (nwilliams) wrote : | # |
Thanks for clarifications and tweaks.
As discussed in scrum, even though there may be a lingering issue this does fix a number of problems and will detect more. Approving now so nightly, etc tests can utilize it.
Akiban Build User (build-akiban) wrote : | # |
There was one failure during build/test:
* unknown exception (check log)
Nathan Williams (nwilliams) wrote : | # |
LBJ timeout.
Preview Diff
1 | === modified file 'src/main/java/com/persistit/Buffer.java' | |||
2 | --- src/main/java/com/persistit/Buffer.java 2012-08-31 14:06:47 +0000 | |||
3 | +++ src/main/java/com/persistit/Buffer.java 2012-09-27 18:35:29 +0000 | |||
4 | @@ -426,6 +426,7 @@ | |||
5 | 426 | * Initializes the buffer so that it contains no keys or data. | 426 | * Initializes the buffer so that it contains no keys or data. |
6 | 427 | */ | 427 | */ |
7 | 428 | void init(final int type) { | 428 | void init(final int type) { |
8 | 429 | assert isOwnedAsWriterByMe(); | ||
9 | 429 | _type = type; | 430 | _type = type; |
10 | 430 | setKeyBlockEnd(KEY_BLOCK_START); | 431 | setKeyBlockEnd(KEY_BLOCK_START); |
11 | 431 | _tailHeaderSize = isIndexPage() ? TAILBLOCK_HDR_SIZE_INDEX : TAILBLOCK_HDR_SIZE_DATA; | 432 | _tailHeaderSize = isIndexPage() ? TAILBLOCK_HDR_SIZE_INDEX : TAILBLOCK_HDR_SIZE_DATA; |
12 | @@ -462,7 +463,7 @@ | |||
13 | 462 | } | 463 | } |
14 | 463 | 464 | ||
15 | 464 | void load() throws InvalidPageStructureException { | 465 | void load() throws InvalidPageStructureException { |
17 | 465 | Debug.$assert0.t(isMine()); | 466 | Debug.$assert0.t(isOwnedAsWriterByMe()); |
18 | 466 | 467 | ||
19 | 467 | _timestamp = getLong(TIMESTAMP_OFFSET); | 468 | _timestamp = getLong(TIMESTAMP_OFFSET); |
20 | 468 | 469 | ||
21 | @@ -502,7 +503,7 @@ | |||
22 | 502 | } | 503 | } |
23 | 503 | 504 | ||
24 | 504 | void writePageOnCheckpoint(final long timestamp) throws PersistitException { | 505 | void writePageOnCheckpoint(final long timestamp) throws PersistitException { |
26 | 505 | Debug.$assert0.t(isMine()); | 506 | Debug.$assert0.t(isOwnedAsWriterByMe()); |
27 | 506 | final long checkpointTimestamp = _persistit.getTimestampAllocator().getProposedCheckpointTimestamp(); | 507 | final long checkpointTimestamp = _persistit.getTimestampAllocator().getProposedCheckpointTimestamp(); |
28 | 507 | if (isDirty() && !isTemporary() && getTimestamp() < checkpointTimestamp && timestamp > checkpointTimestamp) { | 508 | if (isDirty() && !isTemporary() && getTimestamp() < checkpointTimestamp && timestamp > checkpointTimestamp) { |
29 | 508 | writePage(false); | 509 | writePage(false); |
30 | @@ -514,8 +515,8 @@ | |||
31 | 514 | writePage(_persistit.getJournalManager().isWritePagePruningEnabled()); | 515 | writePage(_persistit.getJournalManager().isWritePagePruningEnabled()); |
32 | 515 | } | 516 | } |
33 | 516 | 517 | ||
36 | 517 | private void writePage(final boolean prune) throws PersistitException { | 518 | void writePage(final boolean prune) throws PersistitException { |
37 | 518 | assert isMine(); | 519 | assert isOwnedAsWriterByMe(); |
38 | 519 | _persistit.checkFatal(); | 520 | _persistit.checkFatal(); |
39 | 520 | final Volume volume = getVolume(); | 521 | final Volume volume = getVolume(); |
40 | 521 | if (volume != null) { | 522 | if (volume != null) { |
41 | @@ -546,7 +547,7 @@ | |||
42 | 546 | } | 547 | } |
43 | 547 | 548 | ||
44 | 548 | void setDirtyAtTimestamp(final long timestamp) { | 549 | void setDirtyAtTimestamp(final long timestamp) { |
46 | 549 | if (!isMine()) { | 550 | if (!isOwnedAsWriterByMe()) { |
47 | 550 | throw new IllegalStateException("Exclusive claim required " + this); | 551 | throw new IllegalStateException("Exclusive claim required " + this); |
48 | 551 | } | 552 | } |
49 | 552 | if (super.setDirty()) { | 553 | if (super.setDirty()) { |
50 | @@ -748,7 +749,7 @@ | |||
51 | 748 | * the sibling's address | 749 | * the sibling's address |
52 | 749 | */ | 750 | */ |
53 | 750 | void setRightSibling(final long pageAddress) { | 751 | void setRightSibling(final long pageAddress) { |
55 | 751 | Debug.$assert0.t(isMine()); | 752 | Debug.$assert0.t(isOwnedAsWriterByMe()); |
56 | 752 | _rightSibling = pageAddress; | 753 | _rightSibling = pageAddress; |
57 | 753 | } | 754 | } |
58 | 754 | 755 | ||
59 | @@ -1181,6 +1182,25 @@ | |||
60 | 1181 | return pointer; | 1182 | return pointer; |
61 | 1182 | } | 1183 | } |
62 | 1183 | 1184 | ||
63 | 1185 | void setLongRecordPointer(final int foundAt, final long pointer) { | ||
64 | 1186 | assert isDataPage() : "Invalid page type for long records: " + this; | ||
65 | 1187 | final int kbData = getInt(foundAt & P_MASK); | ||
66 | 1188 | final int tail = decodeKeyBlockTail(kbData); | ||
67 | 1189 | final int tbData = getInt(tail); | ||
68 | 1190 | final int klength = decodeTailBlockKLength(tbData); | ||
69 | 1191 | final int size = decodeTailBlockSize(tbData); | ||
70 | 1192 | final int valueSize = size - klength - _tailHeaderSize; | ||
71 | 1193 | if (valueSize != LONGREC_SIZE) { | ||
72 | 1194 | return; | ||
73 | 1195 | } | ||
74 | 1196 | if ((_bytes[tail + _tailHeaderSize + klength] & 0xFF) != LONGREC_TYPE) { | ||
75 | 1197 | return; | ||
76 | 1198 | } | ||
77 | 1199 | |||
78 | 1200 | putLong(tail + _tailHeaderSize + klength + LONGREC_PAGE_OFFSET, (int) pointer); | ||
79 | 1201 | |||
80 | 1202 | } | ||
81 | 1203 | |||
82 | 1184 | long getPointer(final int foundAt) throws PersistitException { | 1204 | long getPointer(final int foundAt) throws PersistitException { |
83 | 1185 | if (!isIndexPage()) { | 1205 | if (!isIndexPage()) { |
84 | 1186 | throw new InvalidPageTypeException("type=" + _type); | 1206 | throw new InvalidPageTypeException("type=" + _type); |
85 | @@ -3041,7 +3061,7 @@ | |||
86 | 3041 | * Repacks the tail blocks so that they are contiguous. | 3061 | * Repacks the tail blocks so that they are contiguous. |
87 | 3042 | */ | 3062 | */ |
88 | 3043 | private void repack() { | 3063 | private void repack() { |
90 | 3044 | Debug.$assert0.t(isMine()); | 3064 | Debug.$assert0.t(isOwnedAsWriterByMe()); |
91 | 3045 | 3065 | ||
92 | 3046 | final int[] plan = getRepackPlanBuffer(); | 3066 | final int[] plan = getRepackPlanBuffer(); |
93 | 3047 | // | 3067 | // |
94 | @@ -3564,7 +3584,7 @@ | |||
95 | 3564 | try { | 3584 | try { |
96 | 3565 | boolean hasLongMvvRecords = false; | 3585 | boolean hasLongMvvRecords = false; |
97 | 3566 | 3586 | ||
99 | 3567 | if (!isMine()) { | 3587 | if (!isOwnedAsWriterByMe()) { |
100 | 3568 | throw new IllegalStateException("Exclusive claim required " + this); | 3588 | throw new IllegalStateException("Exclusive claim required " + this); |
101 | 3569 | } | 3589 | } |
102 | 3570 | if (isDataPage() && _mvvCount != 0) { | 3590 | if (isDataPage() && _mvvCount != 0) { |
103 | @@ -3898,7 +3918,7 @@ | |||
104 | 3898 | r.getKbOffset(), r.getDb(), r.getEbc(), r.getTbOffset(), r.getKLength(), keyString, | 3918 | r.getKbOffset(), r.getDb(), r.getEbc(), r.getTbOffset(), r.getKLength(), keyString, |
105 | 3899 | r.getValueState().getEncodedBytes().length, valueString)); | 3919 | r.getValueState().getEncodedBytes().length, valueString)); |
106 | 3900 | } else { | 3920 | } else { |
108 | 3901 | sb.append(String.format("\n%s %5d: db=%3d ebc=%3d tb=%,5d [%,d]%s->%,d %s", mark, | 3921 | sb.append(String.format("\n%s %5d: db=%3d ebc=%3d tb=%,5d [%,d]%s->%,d", mark, |
109 | 3902 | r.getKbOffset(), r.getDb(), r.getEbc(), r.getTbOffset(), r.getKLength(), keyString, | 3922 | r.getKbOffset(), r.getDb(), r.getEbc(), r.getTbOffset(), r.getKLength(), keyString, |
110 | 3903 | r.getPointerValue())); | 3923 | r.getPointerValue())); |
111 | 3904 | } | 3924 | } |
112 | @@ -4053,6 +4073,8 @@ | |||
113 | 4053 | if (_alloc - GARBAGE_BLOCK_SIZE < _keyBlockEnd) { | 4073 | if (_alloc - GARBAGE_BLOCK_SIZE < _keyBlockEnd) { |
114 | 4054 | return false; | 4074 | return false; |
115 | 4055 | } else { | 4075 | } else { |
116 | 4076 | assert !chainIsRedundant(left) : "Attempting to add a redundate garbage chain " + left + "->" + right | ||
117 | 4077 | + " to " + this; | ||
118 | 4056 | _alloc -= GARBAGE_BLOCK_SIZE; | 4078 | _alloc -= GARBAGE_BLOCK_SIZE; |
119 | 4057 | putInt(_alloc + GARBAGE_BLOCK_STATUS, 0); | 4079 | putInt(_alloc + GARBAGE_BLOCK_STATUS, 0); |
120 | 4058 | putLong(_alloc + GARBAGE_BLOCK_LEFT_PAGE, left); | 4080 | putLong(_alloc + GARBAGE_BLOCK_LEFT_PAGE, left); |
121 | @@ -4063,6 +4085,16 @@ | |||
122 | 4063 | } | 4085 | } |
123 | 4064 | } | 4086 | } |
124 | 4065 | 4087 | ||
125 | 4088 | private boolean chainIsRedundant(final long left) { | ||
126 | 4089 | for (int p = _alloc; p < _bufferSize; p += GARBAGE_BLOCK_SIZE) { | ||
127 | 4090 | final long oldLeft = getGarbageChainLeftPage(p); | ||
128 | 4091 | if (oldLeft == left) { | ||
129 | 4092 | return true; | ||
130 | 4093 | } | ||
131 | 4094 | } | ||
132 | 4095 | return false; | ||
133 | 4096 | } | ||
134 | 4097 | |||
135 | 4066 | int getGarbageChainStatus() { | 4098 | int getGarbageChainStatus() { |
136 | 4067 | Debug.$assert0.t(isGarbagePage()); | 4099 | Debug.$assert0.t(isGarbagePage()); |
137 | 4068 | if (_alloc + GARBAGE_BLOCK_SIZE > _bufferSize) | 4100 | if (_alloc + GARBAGE_BLOCK_SIZE > _bufferSize) |
138 | @@ -4106,8 +4138,8 @@ | |||
139 | 4106 | } | 4138 | } |
140 | 4107 | 4139 | ||
141 | 4108 | void setGarbageLeftPage(final long left) { | 4140 | void setGarbageLeftPage(final long left) { |
144 | 4109 | Debug.$assert1.t(isMine() && isGarbagePage() && left > 0 && left <= MAX_VALID_PAGE_ADDR && left != _page | 4141 | Debug.$assert1.t(isOwnedAsWriterByMe() && isGarbagePage() && left > 0 && left <= MAX_VALID_PAGE_ADDR |
145 | 4110 | && _alloc + GARBAGE_BLOCK_SIZE <= _bufferSize && _alloc >= _keyBlockEnd); | 4142 | && left != _page && _alloc + GARBAGE_BLOCK_SIZE <= _bufferSize && _alloc >= _keyBlockEnd); |
146 | 4111 | putLong(_alloc + GARBAGE_BLOCK_LEFT_PAGE, left); | 4143 | putLong(_alloc + GARBAGE_BLOCK_LEFT_PAGE, left); |
147 | 4112 | bumpGeneration(); | 4144 | bumpGeneration(); |
148 | 4113 | } | 4145 | } |
149 | 4114 | 4146 | ||
150 | === modified file 'src/main/java/com/persistit/BufferPool.java' | |||
151 | --- src/main/java/com/persistit/BufferPool.java 2012-09-06 20:53:18 +0000 | |||
152 | +++ src/main/java/com/persistit/BufferPool.java 2012-09-27 18:35:29 +0000 | |||
153 | @@ -631,7 +631,7 @@ | |||
154 | 631 | } | 631 | } |
155 | 632 | 632 | ||
156 | 633 | private void invalidate(final Buffer buffer) { | 633 | private void invalidate(final Buffer buffer) { |
158 | 634 | Debug.$assert0.t(buffer.isValid() && buffer.isMine()); | 634 | Debug.$assert0.t(buffer.isValid() && buffer.isOwnedAsWriterByMe()); |
159 | 635 | 635 | ||
160 | 636 | while (!detach(buffer)) { | 636 | while (!detach(buffer)) { |
161 | 637 | // | 637 | // |
162 | @@ -723,6 +723,7 @@ | |||
163 | 723 | if (buffer.claim(writer, 0)) { | 723 | if (buffer.claim(writer, 0)) { |
164 | 724 | vol.getStatistics().bumpGetCounter(); | 724 | vol.getStatistics().bumpGetCounter(); |
165 | 725 | bumpHitCounter(); | 725 | bumpHitCounter(); |
166 | 726 | assert !buffer.isOwnedAsWriterByOther(); | ||
167 | 726 | return buffer; | 727 | return buffer; |
168 | 727 | } else { | 728 | } else { |
169 | 728 | mustClaim = true; | 729 | mustClaim = true; |
170 | @@ -763,32 +764,42 @@ | |||
171 | 763 | _hashLocks[hash % HASH_LOCKS].unlock(); | 764 | _hashLocks[hash % HASH_LOCKS].unlock(); |
172 | 764 | } | 765 | } |
173 | 765 | if (mustClaim) { | 766 | if (mustClaim) { |
200 | 766 | /* | 767 | boolean claimed = false; |
201 | 767 | * We're here because we found the page we want, but another | 768 | boolean same = true; |
202 | 768 | * thread has an incompatible claim on it. Here we wait, then | 769 | final long expires = System.currentTimeMillis() + SharedResource.DEFAULT_MAX_WAIT_TIME; |
203 | 769 | * recheck to make sure the buffer still represents the same | 770 | while (same && !claimed && System.currentTimeMillis() < expires) { |
204 | 770 | * page. | 771 | /* |
205 | 771 | */ | 772 | * We're here because we found the page we want, but another |
206 | 772 | if (!buffer.claim(writer)) { | 773 | * thread has an incompatible claim on it. Here we wait, |
207 | 773 | throw new InUseException("Thread " + Thread.currentThread().getName() + " failed to acquire " | 774 | * then recheck to make sure the buffer still represents the |
208 | 774 | + (writer ? "writer" : "reader") + " claim on " + buffer); | 775 | * same page. |
209 | 775 | } | 776 | */ |
210 | 776 | 777 | claimed = buffer.claim(writer, Persistit.SHORT_DELAY); | |
211 | 777 | // | 778 | // |
212 | 778 | // Test whether the buffer we picked out is still valid | 779 | // Test whether the buffer we picked out is still valid |
213 | 779 | // | 780 | // |
214 | 780 | if (buffer.isValid() && buffer.getPageAddress() == page && buffer.getVolume() == vol) { | 781 | same = buffer.isValid() && buffer.getPageAddress() == page && buffer.getVolume() == vol; |
215 | 781 | // | 782 | /* |
216 | 782 | // If so, then we're done. | 783 | * Loop will terminate if we got the claim if the page |
217 | 783 | // | 784 | * changed. |
218 | 784 | vol.getStatistics().bumpGetCounter(); | 785 | */ |
219 | 785 | bumpHitCounter(); | 786 | } |
220 | 786 | return buffer; | 787 | if (same) { |
221 | 787 | } | 788 | if (claimed) { |
222 | 788 | // | 789 | // |
223 | 789 | // If not, release the claim and retry. | 790 | // If so, then we're done. |
224 | 790 | // | 791 | // |
225 | 791 | buffer.release(); | 792 | vol.getStatistics().bumpGetCounter(); |
226 | 793 | bumpHitCounter(); | ||
227 | 794 | assert !buffer.isOwnedAsWriterByOther(); | ||
228 | 795 | return buffer; | ||
229 | 796 | } else { | ||
230 | 797 | throw new InUseException("Thread " + Thread.currentThread().getName() + " failed to acquire " | ||
231 | 798 | + (writer ? "writer" : "reader") + " claim on " + buffer); | ||
232 | 799 | } | ||
233 | 800 | } else if (claimed) { | ||
234 | 801 | buffer.release(); | ||
235 | 802 | } | ||
236 | 792 | continue; | 803 | continue; |
237 | 793 | } else { | 804 | } else { |
238 | 794 | /* | 805 | /* |
239 | @@ -976,9 +987,10 @@ | |||
240 | 976 | return buffer; | 987 | return buffer; |
241 | 977 | } | 988 | } |
242 | 978 | // A dirty valid buffer needs to be written and then | 989 | // A dirty valid buffer needs to be written and then |
244 | 979 | // marked invalid | 990 | // marked invalid. Can't prune it before writing it in |
245 | 991 | // this context | ||
246 | 980 | try { | 992 | try { |
248 | 981 | buffer.writePage(); | 993 | buffer.writePage(false); |
249 | 982 | if (detach(buffer)) { | 994 | if (detach(buffer)) { |
250 | 983 | buffer.clearValid(); | 995 | buffer.clearValid(); |
251 | 984 | _forcedWriteCounter.incrementAndGet(); | 996 | _forcedWriteCounter.incrementAndGet(); |
252 | 985 | 997 | ||
253 | === modified file 'src/main/java/com/persistit/CleanupManager.java' | |||
254 | --- src/main/java/com/persistit/CleanupManager.java 2012-09-12 21:16:24 +0000 | |||
255 | +++ src/main/java/com/persistit/CleanupManager.java 2012-09-27 18:35:29 +0000 | |||
256 | @@ -139,7 +139,7 @@ | |||
257 | 139 | } | 139 | } |
258 | 140 | 140 | ||
259 | 141 | @Override | 141 | @Override |
261 | 142 | public long getPollInterval() { | 142 | public long pollInterval() { |
262 | 143 | if (_cleanupActionQueue.size() < DEFAULT_QUEUE_SIZE / 2) { | 143 | if (_cleanupActionQueue.size() < DEFAULT_QUEUE_SIZE / 2) { |
263 | 144 | return super.getPollInterval(); | 144 | return super.getPollInterval(); |
264 | 145 | } else { | 145 | } else { |
265 | 146 | 146 | ||
266 | === modified file 'src/main/java/com/persistit/Exchange.java' | |||
267 | --- src/main/java/com/persistit/Exchange.java 2012-09-08 20:43:15 +0000 | |||
268 | +++ src/main/java/com/persistit/Exchange.java 2012-09-27 18:35:29 +0000 | |||
269 | @@ -3388,7 +3388,8 @@ | |||
270 | 3388 | _volume.getStructure().harvestLongRecords(buffer1, foundAt1, Integer.MAX_VALUE); | 3388 | _volume.getStructure().harvestLongRecords(buffer1, foundAt1, Integer.MAX_VALUE); |
271 | 3389 | _volume.getStructure().harvestLongRecords(buffer2, 0, foundAt2); | 3389 | _volume.getStructure().harvestLongRecords(buffer2, 0, foundAt2); |
272 | 3390 | 3390 | ||
274 | 3391 | Debug.$assert0.t(_tree.isMine() && buffer1.isMine() && buffer2.isMine()); | 3391 | Debug.$assert0.t(_tree.isOwnedAsWriterByMe() && buffer1.isOwnedAsWriterByMe() |
275 | 3392 | && buffer2.isOwnedAsWriterByMe()); | ||
276 | 3392 | final boolean rebalanced = buffer1.join(buffer2, foundAt1, foundAt2, _spareKey1, | 3393 | final boolean rebalanced = buffer1.join(buffer2, foundAt1, foundAt2, _spareKey1, |
277 | 3393 | _spareKey2, _joinPolicy); | 3394 | _spareKey2, _joinPolicy); |
278 | 3394 | if (buffer1.isDataPage()) { | 3395 | if (buffer1.isDataPage()) { |
279 | @@ -3721,6 +3722,7 @@ | |||
280 | 3721 | 3722 | ||
281 | 3722 | private void checkPageType(final Buffer buffer, final int expectedType, final boolean releaseOnFailure) | 3723 | private void checkPageType(final Buffer buffer, final int expectedType, final boolean releaseOnFailure) |
282 | 3723 | throws PersistitException { | 3724 | throws PersistitException { |
283 | 3725 | assert !buffer.isOwnedAsWriterByOther(); | ||
284 | 3724 | final int type = buffer.getPageType(); | 3726 | final int type = buffer.getPageType(); |
285 | 3725 | if (type != expectedType) { | 3727 | if (type != expectedType) { |
286 | 3726 | if (releaseOnFailure) { | 3728 | if (releaseOnFailure) { |
287 | 3727 | 3729 | ||
288 | === modified file 'src/main/java/com/persistit/IOTaskRunnable.java' | |||
289 | --- src/main/java/com/persistit/IOTaskRunnable.java 2012-08-24 13:57:19 +0000 | |||
290 | +++ src/main/java/com/persistit/IOTaskRunnable.java 2012-09-27 18:35:29 +0000 | |||
291 | @@ -16,6 +16,7 @@ | |||
292 | 16 | package com.persistit; | 16 | package com.persistit; |
293 | 17 | 17 | ||
294 | 18 | import com.persistit.exception.PersistitException; | 18 | import com.persistit.exception.PersistitException; |
295 | 19 | import com.persistit.util.Util; | ||
296 | 19 | 20 | ||
297 | 20 | /** | 21 | /** |
298 | 21 | * Base class for the background threads that perform various IO tasks. | 22 | * Base class for the background threads that perform various IO tasks. |
299 | @@ -62,20 +63,20 @@ | |||
300 | 62 | } | 63 | } |
301 | 63 | } | 64 | } |
302 | 64 | 65 | ||
304 | 65 | public synchronized long getPollInterval() { | 66 | public final synchronized long getPollInterval() { |
305 | 66 | return _pollInterval; | 67 | return _pollInterval; |
306 | 67 | } | 68 | } |
307 | 68 | 69 | ||
309 | 69 | public synchronized void setPollInterval(final long pollInterval) { | 70 | public final synchronized void setPollInterval(final long pollInterval) { |
310 | 70 | _pollInterval = pollInterval; | 71 | _pollInterval = pollInterval; |
311 | 71 | kick(); | 72 | kick(); |
312 | 72 | } | 73 | } |
313 | 73 | 74 | ||
315 | 74 | public synchronized Exception getLastException() { | 75 | public final synchronized Exception getLastException() { |
316 | 75 | return _lastException; | 76 | return _lastException; |
317 | 76 | } | 77 | } |
318 | 77 | 78 | ||
320 | 78 | public synchronized int getExceptionCount() { | 79 | public final synchronized int getExceptionCount() { |
321 | 79 | return _exceptionCount; | 80 | return _exceptionCount; |
322 | 80 | } | 81 | } |
323 | 81 | 82 | ||
324 | @@ -132,7 +133,15 @@ | |||
325 | 132 | _notified = false; | 133 | _notified = false; |
326 | 133 | } | 134 | } |
327 | 134 | try { | 135 | try { |
329 | 135 | runTask(); | 136 | /* |
330 | 137 | * Unit tests use a negative poll interval to prevent processing | ||
331 | 138 | * here | ||
332 | 139 | */ | ||
333 | 140 | if (getPollInterval() < 0) { | ||
334 | 141 | Util.spinSleep(); | ||
335 | 142 | } else { | ||
336 | 143 | runTask(); | ||
337 | 144 | } | ||
338 | 136 | } catch (final Exception e) { | 145 | } catch (final Exception e) { |
339 | 137 | if (lastException(e)) { | 146 | if (lastException(e)) { |
340 | 138 | _persistit.getLogBase().exception.log(e); | 147 | _persistit.getLogBase().exception.log(e); |
341 | 139 | 148 | ||
342 | === modified file 'src/main/java/com/persistit/JournalManager.java' | |||
343 | --- src/main/java/com/persistit/JournalManager.java 2012-09-26 21:30:03 +0000 | |||
344 | +++ src/main/java/com/persistit/JournalManager.java 2012-09-27 18:35:29 +0000 | |||
345 | @@ -2191,7 +2191,7 @@ | |||
346 | 2191 | * activities. | 2191 | * activities. |
347 | 2192 | */ | 2192 | */ |
348 | 2193 | @Override | 2193 | @Override |
350 | 2194 | public long getPollInterval() { | 2194 | public long pollInterval() { |
351 | 2195 | final IOMeter iom = _persistit.getIOMeter(); | 2195 | final IOMeter iom = _persistit.getIOMeter(); |
352 | 2196 | final long pollInterval = super.getPollInterval(); | 2196 | final long pollInterval = super.getPollInterval(); |
353 | 2197 | final int urgency = urgency(); | 2197 | final int urgency = urgency(); |
354 | 2198 | 2198 | ||
355 | === modified file 'src/main/java/com/persistit/MVV.java' | |||
356 | --- src/main/java/com/persistit/MVV.java 2012-08-24 13:57:19 +0000 | |||
357 | +++ src/main/java/com/persistit/MVV.java 2012-09-27 18:35:29 +0000 | |||
358 | @@ -15,7 +15,6 @@ | |||
359 | 15 | 15 | ||
360 | 16 | package com.persistit; | 16 | package com.persistit; |
361 | 17 | 17 | ||
362 | 18 | import static com.persistit.Buffer.LONGREC_PAGE_OFFSET; | ||
363 | 19 | import static com.persistit.Buffer.LONGREC_SIZE; | 18 | import static com.persistit.Buffer.LONGREC_SIZE; |
364 | 20 | import static com.persistit.Buffer.LONGREC_TYPE; | 19 | import static com.persistit.Buffer.LONGREC_TYPE; |
365 | 21 | import static com.persistit.TransactionIndex.vh2ts; | 20 | import static com.persistit.TransactionIndex.vh2ts; |
366 | @@ -493,7 +492,7 @@ | |||
367 | 493 | final long version = getVersion(bytes, from); | 492 | final long version = getVersion(bytes, from); |
368 | 494 | long longRecordPage = 0; | 493 | long longRecordPage = 0; |
369 | 495 | if (vlength == LONGREC_SIZE && (bytes[from + LENGTH_PER_VERSION] & 0xFF) == LONGREC_TYPE) { | 494 | if (vlength == LONGREC_SIZE && (bytes[from + LENGTH_PER_VERSION] & 0xFF) == LONGREC_TYPE) { |
371 | 496 | longRecordPage = Util.getLong(bytes, from + LENGTH_PER_VERSION + LONGREC_PAGE_OFFSET); | 495 | longRecordPage = Buffer.decodeLongRecordDescriptorPointer(bytes, from + LENGTH_PER_VERSION); |
372 | 497 | } | 496 | } |
373 | 498 | if (version != PRIMORDIAL_VALUE_VERSION || longRecordPage != 0) { | 497 | if (version != PRIMORDIAL_VALUE_VERSION || longRecordPage != 0) { |
374 | 499 | final PrunedVersion pv = new PrunedVersion(version, longRecordPage); | 498 | final PrunedVersion pv = new PrunedVersion(version, longRecordPage); |
375 | 500 | 499 | ||
376 | === modified file 'src/main/java/com/persistit/SessionId.java' | |||
377 | --- src/main/java/com/persistit/SessionId.java 2012-08-24 13:57:19 +0000 | |||
378 | +++ src/main/java/com/persistit/SessionId.java 2012-09-27 18:35:29 +0000 | |||
379 | @@ -77,4 +77,13 @@ | |||
380 | 77 | _owner.set(Thread.currentThread()); | 77 | _owner.set(Thread.currentThread()); |
381 | 78 | } | 78 | } |
382 | 79 | 79 | ||
383 | 80 | public String ownerName() { | ||
384 | 81 | final Thread t = _owner.get(); | ||
385 | 82 | if (t == null) { | ||
386 | 83 | return "null"; | ||
387 | 84 | } else { | ||
388 | 85 | return t.getName(); | ||
389 | 86 | } | ||
390 | 87 | } | ||
391 | 88 | |||
392 | 80 | } | 89 | } |
393 | 81 | 90 | ||
394 | === modified file 'src/main/java/com/persistit/SharedResource.java' | |||
395 | --- src/main/java/com/persistit/SharedResource.java 2012-08-24 13:57:19 +0000 | |||
396 | +++ src/main/java/com/persistit/SharedResource.java 2012-09-27 18:35:29 +0000 | |||
397 | @@ -196,7 +196,7 @@ | |||
398 | 196 | if ((state & CLAIMED_MASK) == 1) { | 196 | if ((state & CLAIMED_MASK) == 1) { |
399 | 197 | final int newState = (state - count) & ~WRITER_MASK; | 197 | final int newState = (state - count) & ~WRITER_MASK; |
400 | 198 | // Do this first so that another thread setting | 198 | // Do this first so that another thread setting |
402 | 199 | // a writer claim does not lose it's copy. | 199 | // a writer claim does not lose its copy. |
403 | 200 | setExclusiveOwnerThread(null); | 200 | setExclusiveOwnerThread(null); |
404 | 201 | if (compareAndSetState(state, newState)) { | 201 | if (compareAndSetState(state, newState)) { |
405 | 202 | return newState; | 202 | return newState; |
406 | @@ -294,10 +294,15 @@ | |||
407 | 294 | * | 294 | * |
408 | 295 | * @return <i>true</i> if this Thread has a writer claim on this page. | 295 | * @return <i>true</i> if this Thread has a writer claim on this page. |
409 | 296 | */ | 296 | */ |
411 | 297 | boolean isMine() { | 297 | boolean isOwnedAsWriterByMe() { |
412 | 298 | return (_sync.writerThread() == Thread.currentThread()); | 298 | return (_sync.writerThread() == Thread.currentThread()); |
413 | 299 | } | 299 | } |
414 | 300 | 300 | ||
415 | 301 | boolean isOwnedAsWriterByOther() { | ||
416 | 302 | final Thread t = _sync.writerThread(); | ||
417 | 303 | return t != null && t != Thread.currentThread(); | ||
418 | 304 | } | ||
419 | 305 | |||
420 | 301 | boolean claim(final boolean writer) throws PersistitInterruptedException { | 306 | boolean claim(final boolean writer) throws PersistitInterruptedException { |
421 | 302 | return claim(writer, DEFAULT_MAX_WAIT_TIME); | 307 | return claim(writer, DEFAULT_MAX_WAIT_TIME); |
422 | 303 | } | 308 | } |
423 | 304 | 309 | ||
424 | === modified file 'src/main/java/com/persistit/Tree.java' | |||
425 | --- src/main/java/com/persistit/Tree.java 2012-08-24 13:57:19 +0000 | |||
426 | +++ src/main/java/com/persistit/Tree.java 2012-09-27 18:35:29 +0000 | |||
427 | @@ -133,7 +133,7 @@ | |||
428 | 133 | } | 133 | } |
429 | 134 | 134 | ||
430 | 135 | void changeRootPageAddr(final long rootPageAddr, final int deltaDepth) throws PersistitException { | 135 | void changeRootPageAddr(final long rootPageAddr, final int deltaDepth) throws PersistitException { |
432 | 136 | Debug.$assert0.t(isMine()); | 136 | Debug.$assert0.t(isOwnedAsWriterByMe()); |
433 | 137 | _rootPageAddr = rootPageAddr; | 137 | _rootPageAddr = rootPageAddr; |
434 | 138 | _depth += deltaDepth; | 138 | _depth += deltaDepth; |
435 | 139 | } | 139 | } |
436 | 140 | 140 | ||
437 | === modified file 'src/main/java/com/persistit/Value.java' | |||
438 | --- src/main/java/com/persistit/Value.java 2012-08-24 13:57:19 +0000 | |||
439 | +++ src/main/java/com/persistit/Value.java 2012-09-27 18:35:29 +0000 | |||
440 | @@ -1022,9 +1022,9 @@ | |||
441 | 1022 | private String toStringLongMode() { | 1022 | private String toStringLongMode() { |
442 | 1023 | final StringBuilder sb = new StringBuilder(); | 1023 | final StringBuilder sb = new StringBuilder(); |
443 | 1024 | sb.append("LongRec size="); | 1024 | sb.append("LongRec size="); |
445 | 1025 | sb.append(Util.getLong(_bytes, Buffer.LONGREC_SIZE_OFFSET)); | 1025 | sb.append(Buffer.decodeLongRecordDescriptorSize(_bytes, 0)); |
446 | 1026 | sb.append(" page="); | 1026 | sb.append(" page="); |
448 | 1027 | sb.append(Util.getLong(_bytes, Buffer.LONGREC_PAGE_OFFSET)); | 1027 | sb.append(Buffer.decodeLongRecordDescriptorPointer(_bytes, 0)); |
449 | 1028 | return sb.toString(); | 1028 | return sb.toString(); |
450 | 1029 | } | 1029 | } |
451 | 1030 | 1030 | ||
452 | 1031 | 1031 | ||
453 | === modified file 'src/main/java/com/persistit/VolumeStorageV2.java' | |||
454 | --- src/main/java/com/persistit/VolumeStorageV2.java 2012-08-24 14:00:17 +0000 | |||
455 | +++ src/main/java/com/persistit/VolumeStorageV2.java 2012-09-27 18:35:29 +0000 | |||
456 | @@ -68,7 +68,6 @@ | |||
457 | 68 | import com.persistit.exception.VolumeClosedException; | 68 | import com.persistit.exception.VolumeClosedException; |
458 | 69 | import com.persistit.exception.VolumeFullException; | 69 | import com.persistit.exception.VolumeFullException; |
459 | 70 | import com.persistit.exception.VolumeNotFoundException; | 70 | import com.persistit.exception.VolumeNotFoundException; |
460 | 71 | import com.persistit.util.Debug; | ||
461 | 72 | 71 | ||
462 | 73 | /** | 72 | /** |
463 | 74 | * Manage all details of file I/O for a <code>Volume</code> backing file for | 73 | * Manage all details of file I/O for a <code>Volume</code> backing file for |
464 | @@ -548,7 +547,7 @@ | |||
465 | 548 | @Override | 547 | @Override |
466 | 549 | void flushMetaData() throws PersistitException { | 548 | void flushMetaData() throws PersistitException { |
467 | 550 | if (!isReadOnly()) { | 549 | if (!isReadOnly()) { |
469 | 551 | Debug.$assert1.t(_headBuffer.isMine()); | 550 | assert _headBuffer.isOwnedAsWriterByMe(); |
470 | 552 | final long timestamp = _persistit.getTimestampAllocator().updateTimestamp(); | 551 | final long timestamp = _persistit.getTimestampAllocator().updateTimestamp(); |
471 | 553 | _volume.getStatistics().setLastGlobalTimestamp(timestamp); | 552 | _volume.getStatistics().setLastGlobalTimestamp(timestamp); |
472 | 554 | _headBuffer.writePageOnCheckpoint(timestamp); | 553 | _headBuffer.writePageOnCheckpoint(timestamp); |
473 | 555 | 554 | ||
474 | === modified file 'src/main/java/com/persistit/VolumeStructure.java' | |||
475 | --- src/main/java/com/persistit/VolumeStructure.java 2012-08-24 13:57:19 +0000 | |||
476 | +++ src/main/java/com/persistit/VolumeStructure.java 2012-09-27 18:35:29 +0000 | |||
477 | @@ -44,6 +44,8 @@ | |||
478 | 44 | final static String TREE_STATS = "stats"; | 44 | final static String TREE_STATS = "stats"; |
479 | 45 | final static String TREE_ACCUMULATOR = "totals"; | 45 | final static String TREE_ACCUMULATOR = "totals"; |
480 | 46 | 46 | ||
481 | 47 | final static long INVALID_PAGE_ADDRESS = -1; | ||
482 | 48 | |||
483 | 47 | private final Persistit _persistit; | 49 | private final Persistit _persistit; |
484 | 48 | private final Volume _volume; | 50 | private final Volume _volume; |
485 | 49 | private final int _pageSize; | 51 | private final int _pageSize; |
486 | @@ -55,6 +57,31 @@ | |||
487 | 55 | private final Map<String, WeakReference<Tree>> _treeNameHashMap = new HashMap<String, WeakReference<Tree>>(); | 57 | private final Map<String, WeakReference<Tree>> _treeNameHashMap = new HashMap<String, WeakReference<Tree>>(); |
488 | 56 | private Tree _directoryTree; | 58 | private Tree _directoryTree; |
489 | 57 | 59 | ||
490 | 60 | private static class Chain { | ||
491 | 61 | final long _left; | ||
492 | 62 | final long _right; | ||
493 | 63 | |||
494 | 64 | private Chain(final long left, final long right) { | ||
495 | 65 | _left = left; | ||
496 | 66 | _right = right; | ||
497 | 67 | } | ||
498 | 68 | |||
499 | 69 | private long getLeft() { | ||
500 | 70 | return _left; | ||
501 | 71 | } | ||
502 | 72 | |||
503 | 73 | private long getRight() { | ||
504 | 74 | return _right; | ||
505 | 75 | |||
506 | 76 | } | ||
507 | 77 | |||
508 | 78 | @Override | ||
509 | 79 | public String toString() { | ||
510 | 80 | return String.format("%,d->%,d", _left, _right); | ||
511 | 81 | } | ||
512 | 82 | |||
513 | 83 | } | ||
514 | 84 | |||
515 | 58 | VolumeStructure(final Persistit persistit, final Volume volume, final int pageSize) { | 85 | VolumeStructure(final Persistit persistit, final Volume volume, final int pageSize) { |
516 | 59 | _persistit = persistit; | 86 | _persistit = persistit; |
517 | 60 | _volume = volume; | 87 | _volume = volume; |
518 | @@ -417,19 +444,20 @@ | |||
519 | 417 | Buffer buffer = null; | 444 | Buffer buffer = null; |
520 | 418 | _volume.getStorage().claimHeadBuffer(); | 445 | _volume.getStorage().claimHeadBuffer(); |
521 | 419 | try { | 446 | try { |
522 | 447 | final List<Chain> chains = new ArrayList<Chain>(); | ||
523 | 420 | final long garbageRoot = getGarbageRoot(); | 448 | final long garbageRoot = getGarbageRoot(); |
524 | 421 | if (garbageRoot != 0) { | 449 | if (garbageRoot != 0) { |
525 | 422 | Buffer garbageBuffer = _pool.get(_volume, garbageRoot, true, true); | 450 | Buffer garbageBuffer = _pool.get(_volume, garbageRoot, true, true); |
526 | 423 | try { | 451 | try { |
527 | 424 | final long timestamp = _persistit.getTimestampAllocator().updateTimestamp(); | 452 | final long timestamp = _persistit.getTimestampAllocator().updateTimestamp(); |
528 | 425 | garbageBuffer.writePageOnCheckpoint(timestamp); | 453 | garbageBuffer.writePageOnCheckpoint(timestamp); |
531 | 426 | Debug.$assert0.t(garbageBuffer.isGarbagePage()); | 454 | assert garbageBuffer.isGarbagePage() : "Garbage root page wrong type: " + garbageBuffer; |
530 | 427 | Debug.$assert0.t((garbageBuffer.getStatus() & Buffer.CLAIMED_MASK) == 1); | ||
532 | 428 | 455 | ||
533 | 429 | final long page = garbageBuffer.getGarbageChainLeftPage(); | 456 | final long page = garbageBuffer.getGarbageChainLeftPage(); |
534 | 430 | final long rightPage = garbageBuffer.getGarbageChainRightPage(); | 457 | final long rightPage = garbageBuffer.getGarbageChainRightPage(); |
535 | 431 | 458 | ||
537 | 432 | Debug.$assert0.t(page != 0); | 459 | assert page != 0 && page != garbageRoot : "Garbage chain in garbage page + " + garbageBuffer |
538 | 460 | + " has invalid left page address " + page; | ||
539 | 433 | 461 | ||
540 | 434 | if (page == -1) { | 462 | if (page == -1) { |
541 | 435 | final long newGarbageRoot = garbageBuffer.getRightSibling(); | 463 | final long newGarbageRoot = garbageBuffer.getRightSibling(); |
542 | @@ -440,13 +468,12 @@ | |||
543 | 440 | garbageBuffer = null; | 468 | garbageBuffer = null; |
544 | 441 | } else { | 469 | } else { |
545 | 442 | _persistit.getLogBase().allocateFromGarbageChain.log(page, garbageBufferInfo(garbageBuffer)); | 470 | _persistit.getLogBase().allocateFromGarbageChain.log(page, garbageBufferInfo(garbageBuffer)); |
548 | 443 | final boolean solitaire = rightPage == -1; | 471 | assert rightPage != -1 : "Garbage chain in garbage page + " + garbageBuffer |
549 | 444 | buffer = _pool.get(_volume, page, true, !solitaire); | 472 | + " has invalid right page address " + rightPage; |
550 | 473 | buffer = _pool.get(_volume, page, true, true); | ||
551 | 445 | buffer.writePageOnCheckpoint(timestamp); | 474 | buffer.writePageOnCheckpoint(timestamp); |
552 | 446 | 475 | ||
556 | 447 | Debug.$assert0.t(buffer.getPageAddress() > 0); | 476 | final long nextGarbagePage = buffer.getRightSibling(); |
554 | 448 | |||
555 | 449 | final long nextGarbagePage = solitaire ? -1 : buffer.getRightSibling(); | ||
557 | 450 | 477 | ||
558 | 451 | if (nextGarbagePage == rightPage || nextGarbagePage == 0) { | 478 | if (nextGarbagePage == rightPage || nextGarbagePage == 0) { |
559 | 452 | _persistit.getLogBase().garbageChainDone.log(garbageBufferInfo(garbageBuffer), rightPage); | 479 | _persistit.getLogBase().garbageChainDone.log(garbageBufferInfo(garbageBuffer), rightPage); |
560 | @@ -454,7 +481,8 @@ | |||
561 | 454 | } else { | 481 | } else { |
562 | 455 | _persistit.getLogBase().garbageChainUpdate.log(garbageBufferInfo(garbageBuffer), | 482 | _persistit.getLogBase().garbageChainUpdate.log(garbageBufferInfo(garbageBuffer), |
563 | 456 | nextGarbagePage, rightPage); | 483 | nextGarbagePage, rightPage); |
565 | 457 | Debug.$assert0.t(nextGarbagePage > 0); | 484 | assert nextGarbagePage > 0 : "Deallocated page has invalid right pointer " |
566 | 485 | + nextGarbagePage + " in " + buffer; | ||
567 | 458 | garbageBuffer.setGarbageLeftPage(nextGarbagePage); | 486 | garbageBuffer.setGarbageLeftPage(nextGarbagePage); |
568 | 459 | } | 487 | } |
569 | 460 | garbageBuffer.setDirtyAtTimestamp(timestamp); | 488 | garbageBuffer.setDirtyAtTimestamp(timestamp); |
570 | @@ -465,13 +493,14 @@ | |||
571 | 465 | && buffer.getPageAddress() != _garbageRoot | 493 | && buffer.getPageAddress() != _garbageRoot |
572 | 466 | && buffer.getPageAddress() != _directoryRootPage); | 494 | && buffer.getPageAddress() != _directoryRootPage); |
573 | 467 | 495 | ||
575 | 468 | harvestLongRecords(buffer, 0, Integer.MAX_VALUE); | 496 | harvestLongRecords(buffer, 0, Integer.MAX_VALUE, chains); |
576 | 469 | 497 | ||
577 | 470 | buffer.init(Buffer.PAGE_TYPE_UNALLOCATED); | 498 | buffer.init(Buffer.PAGE_TYPE_UNALLOCATED); |
578 | 471 | buffer.clear(); | 499 | buffer.clear(); |
579 | 472 | return buffer; | 500 | return buffer; |
580 | 473 | } finally { | 501 | } finally { |
581 | 474 | garbageBuffer = releaseBuffer(garbageBuffer); | 502 | garbageBuffer = releaseBuffer(garbageBuffer); |
582 | 503 | deallocateGarbageChain(chains); | ||
583 | 475 | } | 504 | } |
584 | 476 | } | 505 | } |
585 | 477 | } finally { | 506 | } finally { |
586 | @@ -490,84 +519,114 @@ | |||
587 | 490 | } | 519 | } |
588 | 491 | 520 | ||
589 | 492 | void deallocateGarbageChain(final long left, final long right) throws PersistitException { | 521 | void deallocateGarbageChain(final long left, final long right) throws PersistitException { |
591 | 493 | Debug.$assert0.t(left > 0); | 522 | final List<Chain> list = new ArrayList<Chain>(); |
592 | 523 | list.add(new Chain(left, right)); | ||
593 | 524 | deallocateGarbageChain(list); | ||
594 | 525 | } | ||
595 | 526 | |||
596 | 527 | private void deallocateGarbageChain(final List<Chain> chains) throws PersistitException { | ||
597 | 494 | 528 | ||
598 | 495 | _volume.getStorage().claimHeadBuffer(); | 529 | _volume.getStorage().claimHeadBuffer(); |
599 | 496 | |||
600 | 497 | Buffer garbageBuffer = null; | ||
601 | 498 | final long timestamp = _persistit.getTimestampAllocator().updateTimestamp(); | ||
602 | 499 | |||
603 | 500 | try { | 530 | try { |
619 | 501 | final long garbagePage = getGarbageRoot(); | 531 | while (!chains.isEmpty()) { |
620 | 502 | if (garbagePage != 0) { | 532 | final Chain chain = chains.remove(chains.size() - 1); |
621 | 503 | if (left == garbagePage || right == garbagePage) { | 533 | final long left = chain.getLeft(); |
622 | 504 | Debug.$assert0.t(false); | 534 | final long right = chain.getRight(); |
623 | 505 | throw new IllegalStateException("De-allocating page that is already garbage: " + "root=" | 535 | |
624 | 506 | + garbagePage + " left=" + left + " right=" + right); | 536 | assert left > 0 || right < 0 : "Attempt to deallocate invalid garbage chain " + chain; |
625 | 507 | } | 537 | |
626 | 508 | 538 | Buffer garbageBuffer = null; | |
627 | 509 | garbageBuffer = _pool.get(_volume, garbagePage, true, true); | 539 | final long timestamp = _persistit.getTimestampAllocator().updateTimestamp(); |
628 | 510 | garbageBuffer.writePageOnCheckpoint(timestamp); | 540 | |
629 | 511 | 541 | try { | |
630 | 512 | final boolean fits = garbageBuffer.addGarbageChain(left, right, -1); | 542 | final long garbagePage = getGarbageRoot(); |
631 | 513 | 543 | if (garbagePage != 0) { | |
632 | 514 | if (fits) { | 544 | if (left == garbagePage || right == garbagePage) { |
633 | 515 | _persistit.getLogBase().newGarbageChain.log(left, right, garbageBufferInfo(garbageBuffer)); | 545 | Debug.$assert0.t(false); |
634 | 546 | throw new IllegalStateException("De-allocating page that is already garbage: " + "root=" | ||
635 | 547 | + garbagePage + " left=" + left + " right=" + right); | ||
636 | 548 | } | ||
637 | 549 | |||
638 | 550 | garbageBuffer = _pool.get(_volume, garbagePage, true, true); | ||
639 | 551 | garbageBuffer.writePageOnCheckpoint(timestamp); | ||
640 | 552 | |||
641 | 553 | final boolean fits = garbageBuffer.addGarbageChain(left, right, -1); | ||
642 | 554 | |||
643 | 555 | if (fits) { | ||
644 | 556 | _persistit.getLogBase().newGarbageChain.log(left, right, garbageBufferInfo(garbageBuffer)); | ||
645 | 557 | garbageBuffer.setDirtyAtTimestamp(timestamp); | ||
646 | 558 | continue; | ||
647 | 559 | } else { | ||
648 | 560 | _persistit.getLogBase().garbagePageFull.log(left, right, garbageBufferInfo(garbageBuffer)); | ||
649 | 561 | garbageBuffer = releaseBuffer(garbageBuffer); | ||
650 | 562 | } | ||
651 | 563 | } | ||
652 | 564 | garbageBuffer = _pool.get(_volume, left, true, true); | ||
653 | 565 | garbageBuffer.writePageOnCheckpoint(timestamp); | ||
654 | 566 | |||
655 | 567 | assert garbageBuffer.isDataPage() || garbageBuffer.isIndexPage() | ||
656 | 568 | || garbageBuffer.isLongRecordPage() : "Attempt to allocate invalid type of page: " | ||
657 | 569 | + garbageBuffer; | ||
658 | 570 | |||
659 | 571 | final long nextGarbagePage = garbageBuffer.getRightSibling(); | ||
660 | 572 | |||
661 | 573 | assert nextGarbagePage > 0 || right == 0 : "Attempt to deallcoate broken chain " + chain | ||
662 | 574 | + " starting at left page " + garbageBuffer; | ||
663 | 575 | Debug.$assert0.t(nextGarbagePage > 0 || right == 0); | ||
664 | 576 | |||
665 | 577 | harvestLongRecords(garbageBuffer, 0, Integer.MAX_VALUE, chains); | ||
666 | 578 | |||
667 | 579 | garbageBuffer.init(Buffer.PAGE_TYPE_GARBAGE); | ||
668 | 580 | |||
669 | 581 | _persistit.getLogBase().newGarbageRoot.log(garbageBufferInfo(garbageBuffer)); | ||
670 | 582 | |||
671 | 583 | if (nextGarbagePage != right) { | ||
672 | 584 | // Will always fit because this is a freshly initialized | ||
673 | 585 | // page | ||
674 | 586 | garbageBuffer.addGarbageChain(nextGarbagePage, right, -1); | ||
675 | 587 | _persistit.getLogBase().newGarbageChain.log(nextGarbagePage, right, | ||
676 | 588 | garbageBufferInfo(garbageBuffer)); | ||
677 | 589 | } | ||
678 | 590 | garbageBuffer.setRightSibling(garbagePage); | ||
679 | 516 | garbageBuffer.setDirtyAtTimestamp(timestamp); | 591 | garbageBuffer.setDirtyAtTimestamp(timestamp); |
684 | 517 | return; | 592 | setGarbageRoot(garbageBuffer.getPageAddress()); |
685 | 518 | } else { | 593 | } finally { |
686 | 519 | _persistit.getLogBase().garbagePageFull.log(left, right, garbageBufferInfo(garbageBuffer)); | 594 | if (garbageBuffer != null) { |
687 | 520 | garbageBuffer = releaseBuffer(garbageBuffer); | 595 | garbageBuffer.releaseTouched(); |
688 | 596 | } | ||
689 | 521 | } | 597 | } |
690 | 522 | } | 598 | } |
691 | 523 | final boolean solitaire = (right == -1); | ||
692 | 524 | garbageBuffer = _pool.get(_volume, left, true, !solitaire); | ||
693 | 525 | garbageBuffer.writePageOnCheckpoint(timestamp); | ||
694 | 526 | |||
695 | 527 | Debug.$assert0.t((garbageBuffer.isDataPage() || garbageBuffer.isIndexPage()) | ||
696 | 528 | || garbageBuffer.isLongRecordPage() || (solitaire && garbageBuffer.isUnallocatedPage())); | ||
697 | 529 | |||
698 | 530 | final long nextGarbagePage = solitaire ? 0 : garbageBuffer.getRightSibling(); | ||
699 | 531 | |||
700 | 532 | Debug.$assert0.t(nextGarbagePage > 0 || right == 0 || solitaire); | ||
701 | 533 | |||
702 | 534 | harvestLongRecords(garbageBuffer, 0, Integer.MAX_VALUE); | ||
703 | 535 | |||
704 | 536 | garbageBuffer.init(Buffer.PAGE_TYPE_GARBAGE); | ||
705 | 537 | |||
706 | 538 | _persistit.getLogBase().newGarbageRoot.log(garbageBufferInfo(garbageBuffer)); | ||
707 | 539 | |||
708 | 540 | if (!solitaire && nextGarbagePage != right) { | ||
709 | 541 | // Will always fit because this is a freshly initialized page | ||
710 | 542 | garbageBuffer.addGarbageChain(nextGarbagePage, right, -1); | ||
711 | 543 | _persistit.getLogBase().newGarbageChain.log(nextGarbagePage, right, garbageBufferInfo(garbageBuffer)); | ||
712 | 544 | } | ||
713 | 545 | garbageBuffer.setRightSibling(garbagePage); | ||
714 | 546 | garbageBuffer.setDirtyAtTimestamp(timestamp); | ||
715 | 547 | setGarbageRoot(garbageBuffer.getPageAddress()); | ||
716 | 548 | } finally { | 599 | } finally { |
717 | 549 | if (garbageBuffer != null) { | ||
718 | 550 | garbageBuffer.releaseTouched(); | ||
719 | 551 | } | ||
720 | 552 | _volume.getStorage().releaseHeadBuffer(); | 600 | _volume.getStorage().releaseHeadBuffer(); |
721 | 553 | } | 601 | } |
722 | 554 | } | 602 | } |
723 | 555 | 603 | ||
727 | 556 | // TODO - no one needs the return value | 604 | void harvestLongRecords(final Buffer buffer, final int start, final int end) throws PersistitException { |
728 | 557 | boolean harvestLongRecords(final Buffer buffer, final int start, final int end) throws PersistitException { | 605 | final List<Chain> chains = new ArrayList<Chain>(); |
729 | 558 | boolean anyLongRecords = false; | 606 | harvestLongRecords(buffer, start, end, chains); |
730 | 607 | deallocateGarbageChain(chains); | ||
731 | 608 | } | ||
732 | 609 | |||
733 | 610 | private void harvestLongRecords(final Buffer buffer, final int start, final int end, final List<Chain> chains) | ||
734 | 611 | throws PersistitException { | ||
735 | 612 | assert buffer.isOwnedAsWriterByMe() : "Harvesting from page owned by another thread: " + buffer; | ||
736 | 559 | if (buffer.isDataPage()) { | 613 | if (buffer.isDataPage()) { |
737 | 560 | final int p1 = buffer.toKeyBlock(start); | 614 | final int p1 = buffer.toKeyBlock(start); |
738 | 561 | final int p2 = buffer.toKeyBlock(end); | 615 | final int p2 = buffer.toKeyBlock(end); |
739 | 562 | for (int p = p1; p < p2 && p != -1; p = buffer.nextKeyBlock(p)) { | 616 | for (int p = p1; p < p2 && p != -1; p = buffer.nextKeyBlock(p)) { |
740 | 563 | final long pointer = buffer.fetchLongRecordPointer(p); | 617 | final long pointer = buffer.fetchLongRecordPointer(p); |
741 | 618 | assert pointer != INVALID_PAGE_ADDRESS : "Long record at keyblock " + p | ||
742 | 619 | + " was already harvested from " + buffer; | ||
743 | 564 | if (pointer != 0) { | 620 | if (pointer != 0) { |
746 | 565 | deallocateGarbageChain(pointer, 0); | 621 | chains.add(new Chain(pointer, 0)); |
747 | 566 | anyLongRecords |= true; | 622 | /* |
748 | 623 | * Detects whether and prevents same pointer from being read | ||
749 | 624 | * and deallocated twice. | ||
750 | 625 | */ | ||
751 | 626 | buffer.setLongRecordPointer(p, INVALID_PAGE_ADDRESS); | ||
752 | 567 | } | 627 | } |
753 | 568 | } | 628 | } |
754 | 569 | } | 629 | } |
755 | 570 | return anyLongRecords; | ||
756 | 571 | } | 630 | } |
757 | 572 | 631 | ||
758 | 573 | private Buffer releaseBuffer(final Buffer buffer) { | 632 | private Buffer releaseBuffer(final Buffer buffer) { |
759 | 574 | 633 | ||
760 | === modified file 'src/test/java/com/persistit/FastIndexTest.java' | |||
761 | --- src/test/java/com/persistit/FastIndexTest.java 2012-08-24 13:57:19 +0000 | |||
762 | +++ src/test/java/com/persistit/FastIndexTest.java 2012-09-27 18:35:29 +0000 | |||
763 | @@ -27,7 +27,7 @@ | |||
764 | 27 | 27 | ||
765 | 28 | private Buffer getABuffer() throws Exception { | 28 | private Buffer getABuffer() throws Exception { |
766 | 29 | final Exchange exchange = _persistit.getExchange("persistit", "FastIndexTest", true); | 29 | final Exchange exchange = _persistit.getExchange("persistit", "FastIndexTest", true); |
768 | 30 | return exchange.getBufferPool().get(exchange.getVolume(), 1, false, true); | 30 | return exchange.getBufferPool().get(exchange.getVolume(), 1, true, true); |
769 | 31 | } | 31 | } |
770 | 32 | 32 | ||
771 | 33 | @Test | 33 | @Test |
772 | 34 | 34 | ||
773 | === modified file 'src/test/java/com/persistit/MVCCPruneBufferTest.java' | |||
774 | --- src/test/java/com/persistit/MVCCPruneBufferTest.java 2012-08-24 13:57:19 +0000 | |||
775 | +++ src/test/java/com/persistit/MVCCPruneBufferTest.java 2012-09-27 18:35:29 +0000 | |||
776 | @@ -143,14 +143,35 @@ | |||
777 | 143 | } | 143 | } |
778 | 144 | 144 | ||
779 | 145 | @Test | 145 | @Test |
781 | 146 | public void testPruneLongRecordsSimple() throws Exception { | 146 | public void testPruneLongRecordsSplit() throws Exception { |
782 | 147 | disableBackgroundCleanup(); | 147 | disableBackgroundCleanup(); |
787 | 148 | trx1.begin(); | 148 | ex1.to("x").store(); |
788 | 149 | storeLongMVV(ex1, "x"); | 149 | trx1.begin(); |
789 | 150 | trx1.commit(); | 150 | for (int k = 2;; k += 2) { |
790 | 151 | trx1.end(); | 151 | if (ex1.fetchBufferCopy(0).getAvailableSize() < 200) { |
791 | 152 | break; | ||
792 | 153 | } | ||
793 | 154 | trx1.setStep(0); | ||
794 | 155 | storeLongMVV(ex1, k); | ||
795 | 156 | trx1.setStep(1); | ||
796 | 157 | store(ex1, k, RED_FOX); | ||
797 | 158 | } | ||
798 | 159 | trx1.commit(); | ||
799 | 160 | trx1.end(); | ||
800 | 161 | |||
801 | 162 | trx1.begin(); | ||
802 | 163 | for (int k = 1; k < 20; k += 2) { | ||
803 | 164 | store(ex1, k, RED_FOX.toUpperCase()); | ||
804 | 165 | } | ||
805 | 166 | trx1.commit(); | ||
806 | 167 | trx1.end(); | ||
807 | 168 | |||
808 | 169 | ex1.to(Key.BEFORE); | ||
809 | 170 | while (ex1.next()) { | ||
810 | 171 | System.out.println(String.format("%10s %s", ex1.getKey(), ex1.getValue())); | ||
811 | 172 | } | ||
812 | 173 | |||
813 | 152 | _persistit.getTransactionIndex().cleanup(); | 174 | _persistit.getTransactionIndex().cleanup(); |
814 | 153 | ex1.prune(); | ||
815 | 154 | assertTrue("Should no longer be an MVV", !ex1.isValueLongMVV()); | 175 | assertTrue("Should no longer be an MVV", !ex1.isValueLongMVV()); |
816 | 155 | } | 176 | } |
817 | 156 | 177 | ||
818 | 157 | 178 | ||
819 | === added file 'src/test/java/com/persistit/VolumeStructureTest.java' | |||
820 | --- src/test/java/com/persistit/VolumeStructureTest.java 1970-01-01 00:00:00 +0000 | |||
821 | +++ src/test/java/com/persistit/VolumeStructureTest.java 2012-09-27 18:35:29 +0000 | |||
822 | @@ -0,0 +1,104 @@ | |||
823 | 1 | /** | ||
824 | 2 | * Copyright © 2011-2012 Akiban Technologies, Inc. All rights reserved. | ||
825 | 3 | * | ||
826 | 4 | * This program and the accompanying materials are made available | ||
827 | 5 | * under the terms of the Eclipse Public License v1.0 which | ||
828 | 6 | * accompanies this distribution, and is available at | ||
829 | 7 | * http://www.eclipse.org/legal/epl-v10.html | ||
830 | 8 | * | ||
831 | 9 | * This program may also be available under different license terms. | ||
832 | 10 | * For more information, see www.akiban.com or contact licensing@akiban.com. | ||
833 | 11 | * | ||
834 | 12 | * Contributors: | ||
835 | 13 | * Akiban Technologies, Inc. | ||
836 | 14 | */ | ||
837 | 15 | |||
838 | 16 | package com.persistit; | ||
839 | 17 | |||
840 | 18 | import static org.junit.Assert.assertEquals; | ||
841 | 19 | |||
842 | 20 | import org.junit.Test; | ||
843 | 21 | |||
844 | 22 | import com.persistit.exception.PersistitException; | ||
845 | 23 | import com.persistit.unit.UnitTestProperties; | ||
846 | 24 | |||
847 | 25 | public class VolumeStructureTest extends PersistitUnitTestCase { | ||
848 | 26 | |||
849 | 27 | private Exchange exchange() throws PersistitException { | ||
850 | 28 | return _persistit.getExchange(UnitTestProperties.VOLUME_NAME, "VolumeStructureTest", true); | ||
851 | 29 | } | ||
852 | 30 | |||
853 | 31 | private long nextAvailable() { | ||
854 | 32 | return _persistit.getVolume(UnitTestProperties.VOLUME_NAME).getNextAvailablePage(); | ||
855 | 33 | } | ||
856 | 34 | |||
857 | 35 | @Test | ||
858 | 36 | public void pagesAreActuallyDeallocated() throws Exception { | ||
859 | 37 | final Exchange ex = exchange(); | ||
860 | 38 | ex.getValue().put(RED_FOX); | ||
861 | 39 | long nextAvailablePage = -1; | ||
862 | 40 | for (int j = 0; j < 10; j++) { | ||
863 | 41 | for (int i = 1; i < 10000; i++) { | ||
864 | 42 | ex.to(i).store(); | ||
865 | 43 | } | ||
866 | 44 | if (j == 0) { | ||
867 | 45 | nextAvailablePage = nextAvailable(); | ||
868 | 46 | } else { | ||
869 | 47 | assertEquals("removeAll should deallocate all pages", nextAvailablePage, ex.getVolume().getStorage() | ||
870 | 48 | .getNextAvailablePage()); | ||
871 | 49 | } | ||
872 | 50 | for (int i = 1; i < 10000; i++) { | ||
873 | 51 | ex.to(i).remove(); | ||
874 | 52 | } | ||
875 | 53 | } | ||
876 | 54 | } | ||
877 | 55 | |||
878 | 56 | @Test | ||
879 | 57 | public void harvestLongOnFullGarbagePage() throws Exception { | ||
880 | 58 | final Exchange ex = exchange(); | ||
881 | 59 | ex.getValue().put(createString(1000000)); | ||
882 | 60 | ex.to(250).append(Key.BEFORE); | ||
883 | 61 | for (int k = 0; k < 10; k++) { | ||
884 | 62 | ex.to(k).store(); | ||
885 | 63 | } | ||
886 | 64 | ex.clear(); | ||
887 | 65 | final long firstAvailable = nextAvailable(); | ||
888 | 66 | final long until = firstAvailable + nextAvailable() / Buffer.GARBAGE_BLOCK_SIZE; | ||
889 | 67 | ex.getValue().put(RED_FOX); | ||
890 | 68 | int count = 0; | ||
891 | 69 | for (count = 0; nextAvailable() < until; count++) { | ||
892 | 70 | ex.to(count).store(); | ||
893 | 71 | } | ||
894 | 72 | ex.removeAll(); | ||
895 | 73 | |||
896 | 74 | _persistit.checkAllVolumes(); | ||
897 | 75 | } | ||
898 | 76 | |||
899 | 77 | @Test | ||
900 | 78 | public void harvestLong() throws Exception { | ||
901 | 79 | final Exchange ex = exchange(); | ||
902 | 80 | long firstAvailable = -1; | ||
903 | 81 | for (int j = 0; j < 10; j++) { | ||
904 | 82 | |||
905 | 83 | ex.getValue().put(createString(100)); | ||
906 | 84 | for (int i = 0; i < 5000; i++) { | ||
907 | 85 | ex.to(i).store(); | ||
908 | 86 | } | ||
909 | 87 | ex.getValue().put(createString(1000000)); | ||
910 | 88 | for (int i = 200; i < 300; i++) { | ||
911 | 89 | if ((i % 10) == 0) { | ||
912 | 90 | ex.to(i).store(); | ||
913 | 91 | } | ||
914 | 92 | } | ||
915 | 93 | ex.clear(); | ||
916 | 94 | if (j == 0) { | ||
917 | 95 | firstAvailable = nextAvailable(); | ||
918 | 96 | } else { | ||
919 | 97 | System.out.printf("%,d -- %,d\n", firstAvailable, nextAvailable()); | ||
920 | 98 | assertEquals("Lost some pages", firstAvailable, nextAvailable()); | ||
921 | 99 | } | ||
922 | 100 | ex.removeAll(); | ||
923 | 101 | } | ||
924 | 102 | _persistit.checkAllVolumes(); | ||
925 | 103 | } | ||
926 | 104 | } | ||
927 | 0 | 105 | ||
928 | === modified file 'src/test/java/com/persistit/stress/unit/Stress2txn.java' | |||
929 | --- src/test/java/com/persistit/stress/unit/Stress2txn.java 2012-08-24 13:57:19 +0000 | |||
930 | +++ src/test/java/com/persistit/stress/unit/Stress2txn.java 2012-09-27 18:35:29 +0000 | |||
931 | @@ -21,6 +21,7 @@ | |||
932 | 21 | import com.persistit.TransactionRunnable; | 21 | import com.persistit.TransactionRunnable; |
933 | 22 | import com.persistit.Value; | 22 | import com.persistit.Value; |
934 | 23 | import com.persistit.exception.PersistitException; | 23 | import com.persistit.exception.PersistitException; |
935 | 24 | import com.persistit.exception.RebalanceException; | ||
936 | 24 | import com.persistit.exception.RollbackException; | 25 | import com.persistit.exception.RollbackException; |
937 | 25 | import com.persistit.util.ArgParser; | 26 | import com.persistit.util.ArgParser; |
938 | 26 | 27 | ||
939 | @@ -224,7 +225,12 @@ | |||
940 | 224 | _exs.remove(); | 225 | _exs.remove(); |
941 | 225 | _ex.remove(); | 226 | _ex.remove(); |
942 | 226 | addWork(2); | 227 | addWork(2); |
944 | 227 | 228 | } catch (final RebalanceException e) { | |
945 | 229 | // TODO - fix code so that RebalanceExceptions don't | ||
946 | 230 | // occur. | ||
947 | 231 | // For now this is a known problem so don't make the | ||
948 | 232 | // stress test fail | ||
949 | 233 | System.err.println(e + " at " + _exs); | ||
950 | 228 | } catch (final Exception e) { | 234 | } catch (final Exception e) { |
951 | 229 | handleThrowable(e); | 235 | handleThrowable(e); |
952 | 230 | } | 236 | } |
953 | 231 | 237 | ||
954 | === modified file 'src/test/java/com/persistit/stress/unit/Stress6.java' | |||
955 | --- src/test/java/com/persistit/stress/unit/Stress6.java 2012-08-24 13:57:19 +0000 | |||
956 | +++ src/test/java/com/persistit/stress/unit/Stress6.java 2012-09-27 18:35:29 +0000 | |||
957 | @@ -61,7 +61,7 @@ | |||
958 | 61 | for (_repeat = 0; (_repeat < _repeatTotal || isUntilStopped()) && !isStopped(); _repeat++) { | 61 | for (_repeat = 0; (_repeat < _repeatTotal || isUntilStopped()) && !isStopped(); _repeat++) { |
959 | 62 | try { | 62 | try { |
960 | 63 | _exs.getValue().putString(""); | 63 | _exs.getValue().putString(""); |
962 | 64 | final int keyLength = (_repeat) + 10; | 64 | final int keyLength = (_repeat % 2000) + 10; |
963 | 65 | _sb1.setLength(0); | 65 | _sb1.setLength(0); |
964 | 66 | _sb2.setLength(0); | 66 | _sb2.setLength(0); |
965 | 67 | for (int i = 0; i < keyLength; i++) { | 67 | for (int i = 0; i < keyLength; i++) { |
Some of the changes are delicate (new claim strategy, delayed deallocation) but I think make sense. Just a few questions and suggestions below.
Buffer# setLongRecordPo inter() - exits quietly if not a data page. Should that be an error instead?
Buffer# addGarbageChain () - New code is searching through all chains and asserting the first isn't the same as the one being added? Not questioning it, just checking my understanding.
SharedResource# isOther( ) - This checks that a) there is no writer or b) I am the writer, right? Could we javadoc that or change the name (e.g. isMineOrNone())?
Might be useful to add some messages to the new asserts in case they ever fire. For example, the left page addr if right page is -1 in Buffer#allocPage().
Did you change the Jenkins job to run the stress tests with asserts?