/** * Copyright 2012 Akiban Technologies, Inc. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.persistit; import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertTrue; import org.junit.Test; import com.persistit.exception.PersistitException; import com.persistit.unit.UnitTestProperties; /** * https://bugs.launchpad.net/akiban-persistit/+bug/1056489 * * When utilizing the step capability of transactions there appear to be * inconsistencies between reading, writing, removing, and pruning. Particularly * when steps of a single transaction appear out of order in a single MVV. * * An invariant in MVV#storeVersion is that each version added to an MVV must * have a larger ts than any version already present in the MVV. This follows * from ww-dependency validation; if there's a version tsv on the MVV and my ts * is before that tsv, by definition I'm concurrent with it and can't write. * * But, the step handling code breaks that. A transaction can't conflict with * itself, so no code prevents insertion of versions with step numbers out of * sequence. MVV#storeVersion does not rearrange the MVV in step order, and the * pruning code simply keeps the last value found for each concurrent * transaction. Therefore in the test case for key={2}, the AntiValue for the * remove is stored after the value 200 in the MVV. The MVV looks like this: * * [277<277>:20,282#02<UNCOMMITTED>:200,282#01<UNCOMMITTED>:AntiValue{}] * * Pruning will then remove the value at 282#2 and keep 282#1, the AntiValue * from the remove. * */ public class Bug1056489Test extends PersistitUnitTestCase { public void update(final Exchange ex, final int k, final int v) throws Exception { _persistit.getTransaction().setStep(2); ex.getKey().clear().append(k); ex.getValue().clear().put(v); ex.store(); } public void remove(final Exchange ex, final int k) throws Exception { _persistit.getTransaction().setStep(1); ex.getKey().clear().append(k); ex.remove(); } @Test public void mvvStepCheck() throws Exception { final Transaction txn = _persistit.getTransaction(); final Exchange ex = _persistit.getExchange(UnitTestProperties.VOLUME_NAME, "new_tree1", true); txn.begin(); for (int i = 1; i <= 3; ++i) { ex.getKey().clear().append(i); ex.getValue().clear().put(i * 10); ex.store(); } txn.commit(); txn.end(); treeCheck(ex, "Initial", 10, 20, 30); txn.begin(); for (int i = 1; i <= 3; ++i) { ex.clear().append(i); if (i == 2) { update(ex, i, i * 100); remove(ex, i); } else { remove(ex, i); update(ex, i, i * 100); } } treeCheck(ex, "Post update, pre commit", 100, 200, 300); txn.commit(); txn.end(); treeCheck(ex, "Updated, committed", 100, 200, 300); while (_persistit.getCleanupManager().getPerformedCount() < _persistit.getCleanupManager().getAcceptedCount()) { Thread.sleep(100); } treeCheck(ex, "Updated, committed, pruned", 100, 200, 300); _persistit.getTransactionIndex().cleanup(); txn.begin(); treeCheck(ex, "Updated, committed, TI cleaned", 100, 200, 300); ex.clear().append(2); assertTrue("Removed should find key {2}", ex.remove()); txn.commit(); txn.end(); treeCheck(ex, "Updated, committed, TI cleaned, removed", 100, 300); } private void treeCheck(final Exchange ex, final String message, final int... expected) throws PersistitException { ex.clear(); int index = 0; while (ex.next(true)) { assertTrue("Too many keys", index < expected.length); assertEquals("Wrong value", expected[index++], ex.getValue().getInt()); } } }