Merge lp:~pbeaman/akiban-persistit/fix-1058650-ISE-out-of-order-MVV into lp:akiban-persistit

Proposed by Peter Beaman
Status: Superseded
Proposed branch: lp:~pbeaman/akiban-persistit/fix-1058650-ISE-out-of-order-MVV
Merge into: lp:akiban-persistit
Diff against target: 231 lines (+116/-51)
3 files modified
src/main/java/com/persistit/Exchange.java (+74/-50)
src/main/java/com/persistit/MVV.java (+2/-1)
src/main/java/com/persistit/exception/VersionsOutOfOrderException.java (+40/-0)
To merge this branch: bzr merge lp:~pbeaman/akiban-persistit/fix-1058650-ISE-out-of-order-MVV
Reviewer Review Type Date Requested Status
Nathan Williams Approve
Review via email: mp+127340@code.launchpad.net

This proposal has been superseded by a proposal from 2012-10-02.

Description of the change

Fix bug 1058650 IllegalStateException due to out-of-order MVVs. The bug is caused by a race between the prune/wwDependency check/storeVersion sequence in Exchange#storeInternal and a transaction in another thread aborting. If the transaction aborts after pruning, but before wwDependency check, then it's version is left in the MVV. If the other transaction started with a higher timestamp that the current transaction, this condition will be recognized as an out-of-order version sequence in MVV#storeVersion. Currently storeVersion has no way to query the transaction status of the now-aborted transaction and is not equipped to further prune the entries. Rather than modifying that method, the fix is a simple retry loop that repeats the prune operation to get rid of the aborted version. This seems reasonable because the condition occurs so rarely.

I did not add a unit test since sequencing this is a bit tricky with ThreadSequencer, but I did confirm by running the MixtureTxn1 stress test that instances of retry do occur and yield the correct result.

Part of the implementation is a new VersionsOutOfOrderException. As proposed this is a subclass of IllegalStateException, which is permitted (ISE is not final) but perhaps a little funky. I would be happy to change this to subclassing RuntimeException or alternatively returning a distinguished value from storeVersion to signal the retry. Advice is welcome.

Note that the assert statement that detects this condition was added only recently, and the condition in which an aborted transaction's version is out-of-sequence with another version must have occurred often and harmlessly before this. The fix chosen here is to prune that version away rather than leaving it since (a) it's an opportune time to prune, and (b) it keeps the ordering logic and assertion simple.

To post a comment you must log in.
Revision history for this message
Nathan Williams (nwilliams) wrote :

Rare and subtle indeed.

Is this retry loop safe? I'm thinking of something like a prune that really does change the array, so getEncodedSize() != spareSize, and then a failed store. Wouldn't the next call to prune and visit think the array is bigger than it actually is?

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

Thanks for the good find. I think I fixed that by resetting the spareValue encoded size to the pruned size. I also discovered a break statement inside of the new retry loop that was intended to break the outer loop. Added a break label, which is kind of ugly syntax. Tell me if this offends and you see a better alternative.

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

Works for me.

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

Conflicts with other branch

376. By Peter Beaman

Merge from trunk and resolve conflicts

377. By Peter Beaman

Fix format

Unmerged revisions

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/Exchange.java'
2--- src/main/java/com/persistit/Exchange.java 2012-09-28 21:39:44 +0000
3+++ src/main/java/com/persistit/Exchange.java 2012-10-02 20:50:24 +0000
4@@ -52,6 +52,7 @@
5 import com.persistit.exception.RetryException;
6 import com.persistit.exception.RollbackException;
7 import com.persistit.exception.TreeNotFoundException;
8+import com.persistit.exception.VersionsOutOfOrderException;
9 import com.persistit.exception.WWRetryException;
10 import com.persistit.policy.JoinPolicy;
11 import com.persistit.policy.SplitPolicy;
12@@ -251,6 +252,8 @@
13
14 private final static int RIGHT_CLAIMED = 2;
15
16+ private final static int VERSIONS_OUT_OF_ORDER_RETRY_COUNT = 3;
17+
18 private Persistit _persistit;
19
20 private final Key _key;
21@@ -1382,7 +1385,8 @@
22 try {
23
24 Value valueToStore = value;
25- for (;;) {
26+
27+ mainRetryLoop: for (;;) {
28 Debug.$assert0.t(buffer == null);
29 if (Debug.ENABLED) {
30 Debug.suspend();
31@@ -1427,7 +1431,7 @@
32
33 Debug.$assert0.t(valueToStore.getPointerValue() > 0);
34 insertIndexLevel(key, valueToStore);
35- break;
36+ break mainRetryLoop;
37 }
38
39 Debug.$assert0.t(buffer == null);
40@@ -1488,57 +1492,77 @@
41 if (doMVCC) {
42 valueToStore = spareValue;
43 final int valueSize = value.getEncodedSize();
44- /*
45- * If key didn't exist the value is truly
46- * non-existent and not just undefined/zero length
47- */
48- byte[] spareBytes = spareValue.getEncodedBytes();
49- int spareSize = keyExisted ? spareValue.getEncodedSize() : -1;
50- spareSize = MVV.prune(spareBytes, 0, spareSize, _persistit.getTransactionIndex(), false,
51- prunedVersions);
52-
53- final TransactionStatus tStatus = _transaction.getTransactionStatus();
54- final int tStep = _transaction.getStep();
55-
56- if ((options & StoreOptions.ONLY_IF_VISIBLE) != 0) {
57- /*
58- * Could be single visit of all versions but
59- * current TI would still require calls to both
60- * commitStatus() and wwDependency()
61- */
62- _mvvVisitor.initInternal(tStatus, tStep, MvvVisitor.Usage.FETCH);
63- MVV.visitAllVersions(_mvvVisitor, spareBytes, 0, spareSize);
64- final int offset = _mvvVisitor.getOffset();
65- if (!_mvvVisitor.foundVersion()
66- || (_mvvVisitor.getLength() > 0 && spareBytes[offset] == MVV.TYPE_ANTIVALUE)) {
67- // Completely done, nothing to store
68- keyExisted = false;
69+ int retries = VERSIONS_OUT_OF_ORDER_RETRY_COUNT;
70+
71+ for (;;) {
72+ try {
73+ /*
74+ * If key didn't exist the value is truly
75+ * non-existent and not just undefined/zero
76+ * length
77+ */
78+ byte[] spareBytes = spareValue.getEncodedBytes();
79+ int spareSize;
80+ if (keyExisted) {
81+ spareSize = MVV.prune(spareBytes, 0, spareValue.getEncodedSize(),
82+ _persistit.getTransactionIndex(), false, prunedVersions);
83+ spareValue.setEncodedSize(spareSize);
84+ } else {
85+ spareSize = -1;
86+ }
87+
88+ final TransactionStatus tStatus = _transaction.getTransactionStatus();
89+ final int tStep = _transaction.getStep();
90+
91+ if ((options & StoreOptions.ONLY_IF_VISIBLE) != 0) {
92+ /*
93+ * Could be single visit of all versions
94+ * but current TI would still require
95+ * calls to both commitStatus() and
96+ * wwDependency()
97+ */
98+ _mvvVisitor.initInternal(tStatus, tStep, MvvVisitor.Usage.FETCH);
99+ MVV.visitAllVersions(_mvvVisitor, spareBytes, 0, spareSize);
100+ final int offset = _mvvVisitor.getOffset();
101+ if (!_mvvVisitor.foundVersion()
102+ || (_mvvVisitor.getLength() > 0 && spareBytes[offset] == MVV.TYPE_ANTIVALUE)) {
103+ // Completely done, nothing to store
104+ keyExisted = false;
105+ break mainRetryLoop;
106+ }
107+ }
108+
109+ // Visit all versions for ww detection
110+ _mvvVisitor.initInternal(tStatus, tStep, MvvVisitor.Usage.STORE);
111+ MVV.visitAllVersions(_mvvVisitor, spareBytes, 0, spareSize);
112+
113+ final int mvvSize = MVV.estimateRequiredLength(spareBytes, spareSize, valueSize);
114+ spareValue.ensureFit(mvvSize);
115+ spareBytes = spareValue.getEncodedBytes();
116+
117+ final long versionHandle = TransactionIndex.tss2vh(
118+ _transaction.getStartTimestamp(), tStep);
119+ int storedLength = MVV.storeVersion(spareBytes, 0, spareSize, spareBytes.length,
120+ versionHandle, value.getEncodedBytes(), 0, valueSize);
121+
122+ incrementMVVCount = (storedLength & MVV.STORE_EXISTED_MASK) == 0;
123+ storedLength &= MVV.STORE_LENGTH_MASK;
124+ spareValue.setEncodedSize(storedLength);
125+
126+ Debug.$assert0.t(MVV.verify(_persistit.getTransactionIndex(), spareBytes, 0,
127+ storedLength));
128+
129+ if (spareValue.getEncodedSize() > maxSimpleValueSize) {
130+ newLongRecordPointerMVV = getLongRecordHelper().storeLongRecord(spareValue,
131+ _transaction.isActive());
132+ }
133 break;
134+ } catch (final VersionsOutOfOrderException e) {
135+ if (retries <= 0) {
136+ throw e;
137+ }
138 }
139 }
140-
141- // Visit all versions for ww detection
142- _mvvVisitor.initInternal(tStatus, tStep, MvvVisitor.Usage.STORE);
143- MVV.visitAllVersions(_mvvVisitor, spareBytes, 0, spareSize);
144-
145- final int mvvSize = MVV.estimateRequiredLength(spareBytes, spareSize, valueSize);
146- spareValue.ensureFit(mvvSize);
147- spareBytes = spareValue.getEncodedBytes();
148-
149- final long versionHandle = TransactionIndex.tss2vh(_transaction.getStartTimestamp(), tStep);
150- int storedLength = MVV.storeVersion(spareBytes, 0, spareSize, spareBytes.length,
151- versionHandle, value.getEncodedBytes(), 0, valueSize);
152-
153- incrementMVVCount = (storedLength & MVV.STORE_EXISTED_MASK) == 0;
154- storedLength &= MVV.STORE_LENGTH_MASK;
155- spareValue.setEncodedSize(storedLength);
156-
157- Debug.$assert0.t(MVV.verify(_persistit.getTransactionIndex(), spareBytes, 0, storedLength));
158-
159- if (spareValue.getEncodedSize() > maxSimpleValueSize) {
160- newLongRecordPointerMVV = getLongRecordHelper().storeLongRecord(spareValue,
161- _transaction.isActive());
162- }
163 }
164 }
165
166
167=== modified file 'src/main/java/com/persistit/MVV.java'
168--- src/main/java/com/persistit/MVV.java 2012-09-28 21:41:44 +0000
169+++ src/main/java/com/persistit/MVV.java 2012-10-02 20:50:24 +0000
170@@ -27,6 +27,7 @@
171 import com.persistit.exception.PersistitException;
172 import com.persistit.exception.PersistitInterruptedException;
173 import com.persistit.exception.TimeoutException;
174+import com.persistit.exception.VersionsOutOfOrderException;
175 import com.persistit.util.Debug;
176 import com.persistit.util.Util;
177
178@@ -343,7 +344,7 @@
179 }
180 } else if (curVersion > versionHandle) {
181 if (vh2ts(versionHandle) != vh2ts(curVersion)) {
182- throw new IllegalStateException("Versions out of order");
183+ throw new VersionsOutOfOrderException("Versions out of order");
184 }
185 remainder = end - to;
186 break;
187
188=== added file 'src/main/java/com/persistit/exception/VersionsOutOfOrderException.java'
189--- src/main/java/com/persistit/exception/VersionsOutOfOrderException.java 1970-01-01 00:00:00 +0000
190+++ src/main/java/com/persistit/exception/VersionsOutOfOrderException.java 2012-10-02 20:50:24 +0000
191@@ -0,0 +1,40 @@
192+/**
193+ * Copyright © 2005-2012 Akiban Technologies, Inc. All rights reserved.
194+ *
195+ * This program and the accompanying materials are made available
196+ * under the terms of the Eclipse Public License v1.0 which
197+ * accompanies this distribution, and is available at
198+ * http://www.eclipse.org/legal/epl-v10.html
199+ *
200+ * This program may also be available under different license terms.
201+ * For more information, see www.akiban.com or contact licensing@akiban.com.
202+ *
203+ * Contributors:
204+ * Akiban Technologies, Inc.
205+ */
206+
207+package com.persistit.exception;
208+
209+/**
210+ * Thrown by {@link com.persistit.Transaction} when
211+ * {@link com.persistit.Transaction#commit commit} fails, or when
212+ * {@link com.persistit.Transaction#rollback rollback} is invoked.
213+ *
214+ * @version 1.0
215+ */
216+public class VersionsOutOfOrderException extends IllegalStateException {
217+
218+ private static final long serialVersionUID = 8247927727242238555L;
219+
220+ public VersionsOutOfOrderException() {
221+ super();
222+ }
223+
224+ public VersionsOutOfOrderException(final String msg) {
225+ super(msg);
226+ }
227+
228+ public VersionsOutOfOrderException(final Throwable t) {
229+ super(t);
230+ }
231+}

Subscribers

People subscribed via source and target branches