/** * 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.fail; import java.nio.ByteBuffer; import org.junit.Test; import com.persistit.exception.PersistitException; import com.persistit.exception.TreeNotFoundException; /** * Attempt to cover all cases from the pseudo graph below and ensure that the * TransactionIndex, JournalManger#_liveTransactionMap, and any k/v stored are * in the proper state after each step. * * <pre> * * +--> abort -> (done) * | * +-----+ +--> commit -> (done) * (in) -->| seq |--->| * +-----+ +-> restart -> (done) * | * +-> checkpoint -> (out) * * * (seq) (seq) (seq) * | | | * begin --+--> write --+--> writeMany --+--> write --> (seq) * * </pre> * * Note that tests including a RESTART assume that any aborted transaction was * fully pruned and removed from the running state. */ public class TransactionLifetimeTest extends PersistitUnitTestCase { private static final String VOLUME_NAME = "persistit"; private static final String TREE_NAME = "transaction_lifetime_test"; private static final String KEY_PREFIX = "key_"; private static final String VALUE_PREFIX = "value_"; @Override public final void setUp() throws Exception { final long FIVE_MIN_NANOS = 1000000000L * 60 * 5; _persistit.getCheckpointManager().setCheckpointIntervalNanos(FIVE_MIN_NANOS); super.setUp(); _persistit.getJournalManager().setRollbackPruningEnabled(false); _persistit.getJournalManager().setWritePagePruningEnabled(false); } @Test public void testBeginAbort() throws PersistitException { doTest(false, false, ABORT); } @Test public void testBeginCommit() throws PersistitException, InterruptedException { doTest(false, false, COMMIT); } @Test public void testBeginCheckpointAbort() throws PersistitException { doTest(false, false, CHECKPOINT, ABORT); } @Test public void testBeginCheckpointCommit() throws PersistitException { doTest(false, false, CHECKPOINT, COMMIT); } @Test public void testBeginWriteAbort() throws PersistitException { doTest(true, true, WRITE, ABORT); } @Test public void testBeginWriteCommit() throws PersistitException { doTest(false, true, WRITE, COMMIT); } @Test public void testBeginCheckpointWriteAbort() throws PersistitException { doTest(true, true, CHECKPOINT, WRITE, ABORT); } @Test public void testBeginCheckpointWriteCommit() throws PersistitException { doTest(false, true, CHECKPOINT, WRITE, COMMIT); } @Test public void testBeginWriteCheckpointAbort() throws PersistitException { doTest(true, true, WRITE, CHECKPOINT, ABORT); } @Test public void testBeginWriteCheckpointCommit() throws PersistitException { doTest(false, true, WRITE, CHECKPOINT, COMMIT); } @Test public void testBeginWriteCheckpointWriteAbort() throws PersistitException { doTest(true, true, WRITE, CHECKPOINT, WRITE, ABORT); } @Test public void testBeginWriteCheckpointWriteCommit() throws PersistitException { doTest(false, true, WRITE, CHECKPOINT, WRITE, COMMIT); } @Test public void testBeginWriteManyAbort() throws PersistitException { doTest(true, true, WRITE_MANY, ABORT); } @Test public void testBeginWriteManyCommit() throws PersistitException { doTest(false, true, WRITE_MANY, COMMIT); } @Test public void testBeginWriteManyCheckpointAbort() throws PersistitException { doTest(true, true, WRITE_MANY, CHECKPOINT, ABORT); } @Test public void testBeginWriteManyCheckpointCommit() throws PersistitException { doTest(false, true, WRITE_MANY, CHECKPOINT, COMMIT); } @Test public void testBeginWriteManyCheckpointWriteAbort() throws PersistitException { doTest(true, true, WRITE_MANY, CHECKPOINT, WRITE, ABORT); } @Test public void testBeginWriteManyCheckpointWriteCommit() throws PersistitException { doTest(false, true, WRITE_MANY, CHECKPOINT, WRITE, COMMIT); } @Test public void testBeginWriteManyCheckpointWriteManyAbort() throws PersistitException { doTest(true, true, WRITE_MANY, CHECKPOINT, WRITE_MANY, ABORT); } @Test public void testBeginWriteManyCheckpointWriteManyCommit() throws PersistitException { doTest(false, true, WRITE_MANY, CHECKPOINT, WRITE_MANY, COMMIT); } @Test public void testBeginRestart() throws PersistitException { doTest(false, false, RESTART); } @Test public void testBeginAbortRestart() throws PersistitException { doTest(false, false, ABORT, RESTART); } @Test public void testBeginCommitRestart() throws PersistitException { doTest(false, false, COMMIT, RESTART); } @Test public void testBeginWriteAbortRestart() throws PersistitException { doTest(false, false, WRITE, ABORT, RESTART); } @Test public void testBeginWriteCommitRestart() throws PersistitException { doTest(false, false, WRITE, COMMIT, RESTART); } @Test public void testBeginWriteManyAbortRestart() throws PersistitException { doTest(false, false, WRITE_MANY, ABORT, RESTART); } @Test public void testBeginWriteManyCommitRestart() throws PersistitException { doTest(false, false, WRITE_MANY, COMMIT, RESTART); } @Test public void testBeginWriteCheckpointAbortRestart() throws PersistitException { doTest(false, false, WRITE, CHECKPOINT, ABORT, RESTART); } @Test public void testBeginWriteCheckpointCommitRestart() throws PersistitException { doTest(false, false, WRITE, CHECKPOINT, COMMIT, RESTART); } @Test public void testBeginWriteManyAbortCheckpointRestart() throws PersistitException { doTest(false, false, WRITE_MANY, ABORT, CHECKPOINT, RESTART); } @Test public void testBeginWriteManyCommitCheckpointRestart() throws PersistitException { doTest(false, false, WRITE_MANY, COMMIT, CHECKPOINT, RESTART); } @Test public void testBeginWriteManyCheckpointAbortRestart() throws PersistitException { doTest(false, false, WRITE_MANY, CHECKPOINT, ABORT, RESTART); } @Test public void testBeginWriteManyCheckpointCommitRestart() throws PersistitException { doTest(false, false, WRITE_MANY, CHECKPOINT, COMMIT, RESTART); } private static class Node { public Node(final String description) { _description = description; } @Override public String toString() { return _description; } private final String _description; } private static final Node ABORT = new Node("ABORT"); private static final Node COMMIT = new Node("COMMIT"); private static final Node WRITE = new Node("WRITE"); private static final Node WRITE_MANY = new Node("WRITE_MANY"); private static final Node CHECKPOINT = new Node("CHECKPOINT"); private static final Node RESTART = new Node("RESTART"); private static int storeMoreThanTxnBuffer(final Exchange ex, int writeCount) throws PersistitException { final ByteBuffer txnBuffer = ex.getTransaction().getTransactionBuffer(); for (;;) { final int prevPos = txnBuffer.position(); ex.clear().append(KEY_PREFIX + writeCount); ex.getValue().clear().put(VALUE_PREFIX + writeCount); ex.store(); ++writeCount; if (prevPos > txnBuffer.position()) { break; } } return writeCount; } private void checkKeys(final boolean shouldExist, final int writeCount) throws PersistitException { Exchange ex = null; try { ex = _persistit.getExchange(VOLUME_NAME, TREE_NAME, false); } catch (final TreeNotFoundException e) { if (shouldExist && writeCount > 0) { fail("Keys expected but tree does not exist: " + e); } else { return; } } try { for (int i = 0; i < writeCount; ++i) { final String expectedKey = KEY_PREFIX + i; final boolean isDefined = ex.clear().append(expectedKey).isValueDefined(); assertEquals(expectedKey + " exists after test", shouldExist, isDefined); } } finally { _persistit.releaseExchange(ex); } } private void checkTransaction(final String desc, final long ts, final Boolean statusExists, final Boolean liveMapExists) { if (statusExists != null) { _persistit.getTransactionIndex().cleanup(); final TransactionStatus status = _persistit.getTransactionIndex().getStatus(ts); final boolean actualInTxnIndex = status != null; assertEquals("TransactionStatus exists after " + desc + " ", statusExists.booleanValue(), actualInTxnIndex); } if (liveMapExists != null) { final boolean actualInLiveMap = _persistit.getJournalManager().unitTestTxnExistsInLiveMap(ts); assertEquals("Transaction in live map after " + desc + " ", liveMapExists.booleanValue(), actualInLiveMap); } } private void doTest(final boolean expectedInIndex, final boolean expectedInLiveMap, final Node... nodes) throws PersistitException { Exchange ex = _persistit.getExchange(VOLUME_NAME, TREE_NAME, true); Transaction txn = ex.getTransaction(); int writeCount = 0; int fullWriteCount = 0; boolean aborted = false; boolean committed = false; boolean currentInTxnIndex = true; boolean currentInLiveMap = false; String stateDescription = "BEGIN"; txn.begin(); final long ts = txn.getStartTimestamp(); try { for (final Node curNode : nodes) { stateDescription += "," + curNode; if (curNode == ABORT) { txn.rollback(); aborted = true; currentInTxnIndex = (writeCount > 0); currentInLiveMap = (writeCount > 0); fullWriteCount += writeCount; writeCount = 0; } else if (curNode == COMMIT) { txn.commit(); committed = true; currentInTxnIndex = false; currentInLiveMap = (writeCount > 0); fullWriteCount += writeCount; writeCount = 0; } else if (curNode == WRITE) { ex.clear().append(KEY_PREFIX + writeCount); ex.getValue().clear().put(VALUE_PREFIX + writeCount); ex.store(); ++writeCount; } else if (curNode == WRITE_MANY) { writeCount = storeMoreThanTxnBuffer(ex, writeCount); currentInLiveMap = true; } else if (curNode == CHECKPOINT) { final CheckpointManager.Checkpoint cp = _persistit.checkpoint(); assertEquals("Checkpoint successfully written", true, cp != null); currentInLiveMap = (!committed && writeCount > 0) || (aborted && writeCount == 0); } else if (curNode == RESTART) { txn = null; ex = null; currentInTxnIndex = false; currentInLiveMap = false; safeCrashAndRestoreProperties(); _persistit.getJournalManager().setRollbackPruningEnabled(false); _persistit.getJournalManager().setWritePagePruningEnabled(false); } else { fail("Unknown test node: " + curNode); } checkTransaction(stateDescription, ts, currentInTxnIndex, currentInLiveMap); } } finally { if (txn != null) { txn.end(); } } checkTransaction("POST_CONDITION", ts, expectedInIndex, expectedInLiveMap); if (aborted && committed) { fail("Saw both commit AND abort?"); } checkKeys(committed, fullWriteCount); } }