/*
Copyright (C) SYSTAP, LLC DBA Blazegraph 2006-2016. All rights reserved.
Contact:
SYSTAP, LLC DBA Blazegraph
2501 Calvert ST NW #106
Washington, DC 20008
licenses@blazegraph.com
This program is free software; you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation; version 2 of the License.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program; if not, write to the Free Software
Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
*/
/*
* Created on Dec 23, 2008
*/
package com.bigdata.journal;
import java.io.IOException;
import java.util.Properties;
import java.util.concurrent.TimeUnit;
import junit.framework.TestCase2;
import com.bigdata.service.AbstractFederation;
import com.bigdata.service.AbstractTransactionService;
import com.bigdata.service.AbstractTransactionService.TxState;
import com.bigdata.service.CommitTimeIndex;
import com.bigdata.service.TxServiceRunState;
/**
* Unit tests of the {@link AbstractTransactionService} using a mock client.
*
* @author <a href="mailto:thompsonbry@users.sourceforge.net">Bryan Thompson</a>
* @version $Id$
*/
public class TestTransactionService extends TestCase2 {
/**
*
*/
public TestTransactionService() {
}
/**
* @param arg0
*/
public TestTransactionService(String arg0) {
super(arg0);
}
/**
* Implementation uses a mock client.
*/
protected MockTransactionService newFixture() {
return new MockTransactionService(new Properties()).start();
}
/**
* Implementation uses a mock client.
*/
protected MockTransactionService newFixture(final Properties p) {
return new MockTransactionService(p).start();
}
protected static class MockTransactionService extends
AbstractTransactionService {
public MockTransactionService(final Properties p) {
super(p);
}
public MockTransactionService start() {
super.start();
return this;
}
@Override
protected long getReadsOnTime(final long txId) {
return super.getReadsOnTime(txId);
}
@Override
public AbstractFederation<?> getFederation() {
return null;
}
@Override
protected void abortImpl(final TxState state) {
state.setRunState(RunState.Aborted);
}
@Override
protected long commitImpl(final TxState state) throws Exception {
state.setRunState(RunState.Committed);
final long commitTime = nextTimestamp();
notifyCommit(commitTime);
return commitTime;
}
// /**
// * Note: We are not testing distributed commits here so this is not
// * implemented.
// */
// public long prepared(long tx, UUID dataService)
// throws InterruptedException, BrokenBarrierException {
// return 0;
// }
//
// /**
// * Note: We are not testing distributed commits here so this is not
// * implemented.
// */
// public boolean committed(long tx, UUID dataService) throws IOException,
// InterruptedException, BrokenBarrierException {
// return false;
// }
@Override
public long getLastCommitTime() {
return lastCommitTime;
}
private long lastCommitTime = 0L;
protected long findCommitTime(final long timestamp) {
synchronized (commitTimeIndex) {
return commitTimeIndex.find(timestamp);
}
}
protected long findNextCommitTime(long commitTime) {
synchronized (commitTimeIndex) {
return commitTimeIndex.findNext(commitTime);
}
}
private final CommitTimeIndex commitTimeIndex = CommitTimeIndex
.createTransient();
public void notifyCommit(long commitTime) {
synchronized (commitTimeIndex) {
/*
* Add all commit times
*/
commitTimeIndex.add(commitTime);
/*
* Note: commit time notifications can be overlap such that they
* appear out of sequence with respect to their values. This is
* Ok. We just ignore any older commit times. However we do need
* to be synchronized here such that the commit time notices
* themselves are serialized so that we do not miss any.
*/
if (lastCommitTime < commitTime) {
lastCommitTime = commitTime;
}
}
/*
* @todo This is not invoking the behavior in the base class because
* that violates the assumptions of some of the unit tests. Those
* tests were written before notifyCommit() was tasked with
* advancing the releaseTime when there were no active transactions.
* The tests could be rewritten under the new assumptions and then
* this line could be uncommented.
*/
// super.notifyCommit(commitTime);
}
/**
* Awaits the specified run state.
*
* @param expectedRunState
* The expected run state.
*
* @throws InterruptedException
* @throws AssertionError
*/
public void awaitRunState(final TxServiceRunState expectedRunState)
throws InterruptedException {
if (expectedRunState == null)
throw new IllegalArgumentException();
lock.lock();
try {
int i = 0;
while (i < 100) {
if (expectedRunState == getRunState()) {
return;
}
txDeactivate.await(10/* ms */, TimeUnit.MILLISECONDS);
i++;
}
/*
* Note: This will generally fail since we did not achieve the
* desired run state in the loop above.
*/
assertEquals(expectedRunState, getRunState());
} finally {
lock.unlock();
}
}
/**
* Note: This currently waits until at least two milliseconds have
* elapsed. This is a workaround for
* {@link TestTransactionService#test_newTx_readOnly()} until (if) <a
* href= "https://sourceforge.net/apps/trac/bigdata/ticket/145"
* >ISSUE#145 </a> is resolved.
*
* TODO This override of {@link #nextTimestamp()} should be removed once
* that issue is fixed.
*/
@Override
public long nextTimestamp() {
// skip at least one millisecond.
super.nextTimestamp();
/*
* Invoke the behavior on the base class, which has a side-effect on
* the private [lastTimestamp] method.
*/
return super.nextTimestamp();
}
/**
* {@inheritDoc}
* <p>
* Exposed to the test suite.
* <p>
* This version takes the lock since we are controlling concurrency
* explicitly in the test suite. This makes it easier to write the
* tests.
*/
@Override
protected TxState getEarliestActiveTx() {
lock.lock();
try {
return super.getEarliestActiveTx();
} finally {
lock.unlock();
}
}
/**
* {@inheritDoc}
* <p>
* Exposed to the test suite.
*/
@Override
protected TxState getTxState(final long txId) {
return super.getTxState(txId);
}
}
/**
* Create a new read-write tx and then abort it.
* <p>
* Note: New read-write transaction identifiers are assigned using
* {@link ITimestampService#nextTimestamp()}. Therefore they are
* monotonically increasing. New read-write transactions may be created at
* any time - there are no preconditions other than that the transaction
* service is running. Likewise there is no contention other than for the
* next distinct timestamp.
*/
public void test_newTx_readWrite_01() {
final MockTransactionService service = newFixture();
try {
assertEquals(0, service.getActiveCount());
assertEquals(0, service.getReadWriteActiveCount());
assertEquals(0, service.getReadOnlyActiveCount());
assertNull(service.getEarliestActiveTx());
final long t0 = service.nextTimestamp();
final long tx = service.newTx(ITx.UNISOLATED);
final long t1 = service.nextTimestamp();
// read-write transactions use negative timestamps.
assertTrue(TimestampUtility.isReadWriteTx(tx));
// must be greater than a timestamp obtained before the tx.
assertTrue(Math.abs(tx) > t0);
// must be less than a timestamp obtained after the tx.
assertTrue(Math.abs(tx) < t1);
assertEquals(1, service.getActiveCount());
assertEquals(1, service.getReadWriteActiveCount());
assertEquals(0, service.getReadOnlyActiveCount());
assertNotNull(service.getEarliestActiveTx());
assertEquals(tx, service.getEarliestActiveTx().tx);
// TxState object.
final TxState txState = service.getTxState(tx);
{
assertNotNull(txState);
assertEquals(tx, txState.tx);
assertTrue(txState.isActive());
assertFalse(txState.isReadOnly());
assertFalse(txState.isPrepared());
assertFalse(txState.isComplete());
}
service.abort(tx);
assertEquals(0, service.getActiveCount());
assertEquals(1, service.getStartCount());
assertEquals(1, service.getAbortCount());
assertEquals(0, service.getCommitCount());
assertEquals(0, service.getReadWriteActiveCount());
assertEquals(0, service.getReadOnlyActiveCount());
assertNull(service.getEarliestActiveTx());
// TxState object.
{
assertNull(service.getTxState(tx));
assertFalse(txState.isActive());
assertTrue(txState.isAborted());
assertFalse(txState.isCommitted());
assertFalse(txState.isPrepared());
assertTrue(txState.isComplete());
}
} finally {
service.destroy();
}
}
/**
* Create a new read-write tx and then commit it.
*/
public void test_newTx_readWrite_02() {
final MockTransactionService service = newFixture();
try {
assertEquals(0, service.getActiveCount());
assertEquals(0, service.getReadWriteActiveCount());
assertEquals(0, service.getReadOnlyActiveCount());
assertNull(service.getEarliestActiveTx());
final long t0 = service.nextTimestamp();
final long tx = service.newTx(ITx.UNISOLATED);
final long t1 = service.nextTimestamp();
// read-write transactions use negative timestamps.
assertTrue(TimestampUtility.isReadWriteTx(tx));
// must be greater than a timestamp obtained before the tx.
assertTrue(Math.abs(tx) > t0);
// must be less than a timestamp obtained after the tx.
assertTrue(Math.abs(tx) < t1);
assertEquals(1, service.getActiveCount());
assertEquals(1, service.getReadWriteActiveCount());
assertEquals(0, service.getReadOnlyActiveCount());
assertNotNull(service.getEarliestActiveTx());
assertEquals(tx, service.getEarliestActiveTx().tx);
final TxState txState = service.getTxState(tx);
{
assertNotNull(txState);
assertEquals(tx, txState.tx);
assertTrue(txState.isActive());
assertFalse(txState.isReadOnly());
assertFalse(txState.isPrepared());
assertFalse(txState.isComplete());
}
service.commit(tx);
assertEquals(0, service.getActiveCount());
assertEquals(1, service.getStartCount());
assertEquals(0, service.getAbortCount());
assertEquals(1, service.getCommitCount());
assertEquals(0, service.getReadWriteActiveCount());
assertEquals(0, service.getReadOnlyActiveCount());
assertNull(service.getEarliestActiveTx());
// TxState object.
{
assertNull(service.getTxState(tx));
assertFalse(txState.isActive());
assertFalse(txState.isAborted());
assertTrue(txState.isCommitted());
assertFalse(txState.isPrepared());
assertTrue(txState.isComplete());
}
} finally {
service.destroy();
}
}
/**
* Create two read-write transactions and commit both.
*/
public void test_newTx_readWrite_03() {
final MockTransactionService service = newFixture();
try {
assertEquals(0, service.getActiveCount());
assertEquals(0, service.getReadWriteActiveCount());
assertEquals(0, service.getReadOnlyActiveCount());
assertNull(service.getEarliestActiveTx());
final long tx1 = service.newTx(ITx.UNISOLATED);
assertEquals(tx1, service.getEarliestActiveTx().tx);
final TxState txState1 = service.getTxState(tx1);
{
assertNotNull(txState1);
assertEquals(tx1, txState1.tx);
assertTrue(txState1.isActive());
assertFalse(txState1.isReadOnly());
assertFalse(txState1.isPrepared());
assertFalse(txState1.isComplete());
}
final long tx2 = service.newTx(ITx.UNISOLATED);
assertTrue(Math.abs(tx1) < Math.abs(tx2));
/*
* Note: tx1<tx2 so tx1 remains "earliestActive" even though both
* have the same readsOnCommitTime.
*/
assertEquals(tx1, service.getEarliestActiveTx().tx);
final TxState txState2 = service.getTxState(tx2);
{
assertNotNull(txState2);
assertEquals(tx2, txState2.tx);
assertTrue(txState2.isActive());
assertFalse(txState2.isReadOnly());
assertFalse(txState2.isPrepared());
assertFalse(txState2.isComplete());
}
assertEquals(2, service.getActiveCount());
assertEquals(2, service.getReadWriteActiveCount());
assertEquals(0, service.getReadOnlyActiveCount());
service.commit(tx2);
// TxState object.
{
assertNull(service.getTxState(tx2));
assertFalse(txState2.isActive());
assertFalse(txState2.isAborted());
assertTrue(txState2.isCommitted());
assertFalse(txState2.isPrepared());
assertTrue(txState2.isComplete());
}
assertEquals(1, service.getActiveCount());
assertEquals(1, service.getReadWriteActiveCount());
assertEquals(0, service.getReadOnlyActiveCount());
assertEquals(tx1, service.getEarliestActiveTx().tx);
service.commit(tx1);
// TxState object.
{
assertNull(service.getTxState(tx1));
assertFalse(txState1.isActive());
assertFalse(txState1.isAborted());
assertTrue(txState1.isCommitted());
assertFalse(txState1.isPrepared());
assertTrue(txState1.isComplete());
}
assertEquals(0, service.getActiveCount());
assertEquals(2, service.getStartCount());
assertEquals(0, service.getAbortCount());
assertEquals(2, service.getCommitCount());
assertEquals(0, service.getReadWriteActiveCount());
assertEquals(0, service.getReadOnlyActiveCount());
assertNull(service.getEarliestActiveTx());
} finally {
service.destroy();
}
}
/**
* Create a read-write transaction, commit it, and then attempt to re-commit
* it and to abort it - those operations should fail with an
* {@link IllegalStateException}.
*/
public void test_newTx_readWrite_txComplete_postConditions() {
final MockTransactionService service = newFixture();
try {
final long tx = service.newTx(ITx.UNISOLATED);
final TxState txState = service.getTxState(tx);
service.commit(tx);
assertFalse(txState.isPrepared());
assertTrue(txState.isCommitted());
try {
service.commit(tx);
fail("Expecting: "+IllegalStateException.class);
} catch(IllegalStateException ex) {
if (log.isInfoEnabled())
log.info("Ignoring expected exception: " + ex);
}
try {
service.abort(tx);
fail("Expecting: "+IllegalStateException.class);
} catch(IllegalStateException ex) {
if (log.isInfoEnabled())
log.info("Ignoring expected exception: " + ex);
}
// Unchanged by the failed abort.
assertFalse(txState.isPrepared());
assertTrue(txState.isCommitted());
} finally {
service.destroy();
}
}
/**
* Test ability to start a read-committed tx when [lastCommitTime] is
* non-zero.
* <p>
* Note: A "read-committed" transactions is just a shorthand for a read-only
* transaction whose start time is the last commit time on the database. As
* such the abort and commit procedure are the same as for a read-only
* transaction. The only difference is in how the start time of the
* transaction is generated, so that is all we test here.
*
* @throws IOException
*/
public void test_newTx_readCommitted01() throws IOException {
final MockTransactionService service = newFixture();
try {
assertEquals(0, service.getActiveCount());
service.notifyCommit(service.nextTimestamp());
final long lastCommitTime = service.getLastCommitTime();
final long t0 = service.nextTimestamp();
final long tx = service.newTx(ITx.READ_COMMITTED);
final long t1 = service.nextTimestamp();
// verify read-only tx.
assertFalse(TimestampUtility.isReadWriteTx(tx));
// must be GT the lastCommitTime.
assertTrue(Math.abs(tx) > lastCommitTime);
// must be greater than a timestamp obtained before the tx.
assertTrue(Math.abs(tx) > t0);
// must be less than a timestamp obtained after the tx.
assertTrue(Math.abs(tx) < t1);
assertEquals(1, service.getActiveCount());
assertEquals(0, service.getReadWriteActiveCount());
assertEquals(1, service.getReadOnlyActiveCount());
service.commit(tx);
assertEquals(0, service.getActiveCount());
assertEquals(1, service.getStartCount());
assertEquals(0, service.getAbortCount());
assertEquals(1, service.getCommitCount());
assertEquals(0, service.getReadWriteActiveCount());
assertEquals(0, service.getReadOnlyActiveCount());
} finally {
service.destroy();
}
}
/**
* Unit test when [lastCommitTime] is zero.
*/
public void test_newTx_readCommitted02() {
final MockTransactionService service = newFixture();
try {
assertEquals(0, service.getActiveCount());
final long lastCommitTime = service.getLastCommitTime();
final long t0 = service.nextTimestamp();
final long tx = service.newTx(ITx.READ_COMMITTED);
final long t1 = service.nextTimestamp();
// verify read-only tx.
assertFalse(TimestampUtility.isReadWriteTx(tx));
// must be GT the lastCommitTime.
assertTrue(Math.abs(tx) > lastCommitTime);
// must be greater than a timestamp obtained before the tx.
assertTrue(Math.abs(tx) > t0);
// must be less than a timestamp obtained after the tx.
assertTrue(Math.abs(tx) < t1);
assertEquals(1, service.getActiveCount());
assertEquals(0, service.getReadWriteActiveCount());
assertEquals(1, service.getReadOnlyActiveCount());
service.commit(tx);
assertEquals(0, service.getActiveCount());
assertEquals(1, service.getStartCount());
assertEquals(0, service.getAbortCount());
assertEquals(1, service.getCommitCount());
assertEquals(0, service.getReadWriteActiveCount());
assertEquals(0, service.getReadOnlyActiveCount());
} finally {
service.destroy();
}
}
/**
* Unit test for a new read-only transaction.
* <p>
* Read-only transactions are allowed to read on historical commit points of
* the database. The edge case is allowed where the callers timestamp
* exactly corresponds to the lastCommitTime, but it is not permitted to be
* GT the lastCommitTime since that could allow data not yet committed to
* become visible during the transaction (breaking isolation).
* <p>
* A commitTime is identified by looking up the callers timestamp in a log
* of the historical commit times and returning the first historical commit
* time LTE the callers timestamp.
* <p>
* The transaction start time is then chosen from the half-open interval
* <i>commitTime</i> (inclusive lower bound) : <i>nextCommitTime</i>
* (exclusive upper bound).
* <P>
* Note: This test (used to) fail occasionally. This occured if the
* timestamps assigned by the {@link MockTransactionService} are only 1 unit
* apart. When that happens, there are not enough distinct values available
* to allow 2 concurrent read-only transactions. See <a href=
* "https://sourceforge.net/apps/trac/bigdata/ticket/145">ISSUE#145 </a>.
* Also see {@link MockTransactionService#nextTimestamp()} which has been
* overridden to guarantee that there are at least two distinct values such
* that this test will pass.
*/
public void test_newTx_readOnly() throws IOException {
final Properties properties = new Properties();
// setup as an immortal database.
properties.setProperty(
AbstractTransactionService.Options.MIN_RELEASE_AGE,
""+Long.MAX_VALUE);
final MockTransactionService service = newFixture(properties);
try {
// populate the commit log on the service.
final long commitTime = service.nextTimestamp();
final long nextCommitTime = service.nextTimestamp();
service.notifyCommit(commitTime);
assertEquals(commitTime, service.getLastCommitTime());
service.notifyCommit(nextCommitTime);
assertEquals(nextCommitTime, service.getLastCommitTime());
// a read-only tx for the 1st commit point .
final long tx1 = service.newTx(commitTime);
if (log.isInfoEnabled())
log.info("tx1=" + tx1);
assertFalse(TimestampUtility.isReadWriteTx(tx1));
assertTrue(tx1 >= commitTime && tx1 < nextCommitTime);
final TxState txState1 = service.getTxState(tx1);
{
assertEquals(tx1, txState1.getStartTimestamp());
assertTrue(txState1.isActive());
assertTrue(txState1.isReadOnly());
assertFalse(txState1.isPrepared());
assertFalse(txState1.isCommitted());
assertFalse(txState1.isComplete());
}
assertEquals(tx1, service.getEarliestActiveTx().tx);
// another read-only tx for the same commit point.
final long tx2 = service.newTx(commitTime);
if (log.isInfoEnabled())
log.info("tx2=" + tx2);
assertFalse(TimestampUtility.isReadWriteTx(tx2));
assertTrue(tx2 >= commitTime && tx2 < nextCommitTime);
assertNotSame(tx1, tx2);
final TxState txState2 = service.getTxState(tx2);
{
assertEquals(tx2, txState2.getStartTimestamp());
assertTrue(txState2.isActive());
assertTrue(txState2.isReadOnly());
assertFalse(txState2.isPrepared());
assertFalse(txState2.isCommitted());
assertFalse(txState2.isComplete());
}
// earliest active tx is unchanged.
assertEquals(tx1, service.getEarliestActiveTx().tx);
// commit tx1 (releases its start time so that it may be reused).
service.commit(tx1);
// no longer in the [activeTx] map.
assertNull(service.getTxState(tx1));
// earliest active tx was changed.
assertEquals(tx2, service.getEarliestActiveTx().tx);
/*
* Another tx for the same commit point.
*
* Note: This will wind up assigning the same txId that was in use
* by tx1 (the first available txId).
*/
final long tx3 = service.newTx(commitTime);
if (log.isInfoEnabled())
log.info("tx3=" + tx3);
assertFalse(TimestampUtility.isReadWriteTx(tx3));
assertTrue(tx3 >= commitTime && tx3 < nextCommitTime);
// tx3 must be distinct from any active tx.
assertNotSame(tx3, tx2);
// but in fact we should have recycled tx1!
assertEquals(tx1, tx3);
// earliest active tx was changed again (back to tx1's startTime).
assertEquals(tx3, service.getEarliestActiveTx().tx);
} finally {
service.destroy();
}
}
/**
* Unit test in which all possible start times for a read-only transaction
* are used, forcing the caller to block.
*
* @throws IOException
*/
public void test_newTx_readOnly_contention() throws IOException {
final MockTransactionService service = newFixture();
try {
// populate the commit log on the service.
final long commitTime = 10;
final long nextCommitTime = 12;
service.notifyCommit(commitTime);
assertEquals(commitTime,service.getLastCommitTime());
service.notifyCommit(nextCommitTime);
assertEquals(nextCommitTime,service.getLastCommitTime());
// a tx for the commit point whose commitTime is 10.
final long tx1 = service.newTx(commitTime);
if (log.isInfoEnabled())
log.info("tx1="+tx1);
assertTrue(tx1 >= commitTime && tx1 < nextCommitTime);
// another tx for the same commit point.
final long tx2 = service.newTx(commitTime);
if (log.isInfoEnabled())
log.info("tx2=" + tx2);
assertTrue(tx2 >= commitTime && tx2 < nextCommitTime);
assertNotSame(tx1, tx2);
{
/*
* First try to obtain a new tx for the same commit point in a
* thread. This should block. We wait for a bit (in the main
* thread) to make sure that the thread is not progressing and
* then interrupt this thread. This is to prove to ourselves
* that the txService can not grant a tx for this commit point
* right now.
*/
final Thread t = new Thread() {
public void run() {
final long tx3 = service.newTx(commitTime);
fail("Not expecting service to create tx: " + tx3);
}
};
t.start();
try {
Thread.sleep(250);
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
// interrupt thread so that the test will continue.
t.interrupt();
}
{
/*
* Run a thread that sleeps for a moment and then terminates one
* of the transactions that is keeping us from being able to
* allocate a newTx for the desired commit point. Once [tx2] is
* terminated, the main thread should be granted a new tx.
*/
new Thread() {
public void run() {
try {
log.info("sleeping in 2nd thread.");
Thread.sleep(250/* ms */);
log.info("woke up in 2nd thread.");
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
/*
* Terminate a running tx for the desired commit point,
* freeing a timestamp that may be used for the newTx()
* request in the main thread.
*/
log.info("will terminate tx2: " + tx2);
service.commit(tx2);
log.info("did terminate tx2: " + tx2);
}
}.start();
/*
* This should block for a moment while the thread is sleeping
* and then succeed.
*
* Note: The assigned transaction identifier will be the same as
* the transaction identifier for [tx2]. This is because we are
* in fact waiting on that transaction identifier to become free
* so that we can continue.
*/
log.info("requesting another tx for the same commit point");
assertEquals(tx2,service.newTx(commitTime));
log.info("have another tx for that commit point.");
}
} finally {
service.destroy();
}
}
/**
* Verify that you can create a read-only transaction by providing the
* lastCommitTime as the timestamp.
*
* @throws IOException
*/
public void test_newTx_readOnly_timestamp_is_lastCommitTime() throws IOException {
final MockTransactionService service = newFixture();
try {
final long lastCommitTime = 10;
// make this a valid commit time.
service.notifyCommit(lastCommitTime);
assertEquals(lastCommitTime, service.getLastCommitTime());
final long t0 = service.nextTimestamp();
// should be legal.
final long tx = service.newTx(lastCommitTime);
final long t1 = service.nextTimestamp();
assertTrue(tx >= lastCommitTime);
assertTrue(tx > t0 && tx < t1);
// and assign another read-only tx for lastCommitTime.
final long tx1 = service.newTx(lastCommitTime);
final long t2 = service.nextTimestamp();
assertTrue(tx1 >= lastCommitTime);
assertTrue(tx1 > tx);
assertTrue(tx1 > t1 && tx1 < t2);
} finally {
service.destroy();
}
}
/**
* Verify the behavior of the {@link AbstractTransactionService} when there
* are no commit points and a read-only transaction is requested. Since
* there are no commit points, the transaction service will return the next
* timestamp. That value will be GT the requested timestamp and LT any
* commit point (all commit points are in the future).
*/
public void test_newTx_nothingCommitted_readOnlyTx() {
final MockTransactionService service = newFixture();
try {
/*
* Note: The commit time log is empty.
*/
final long timestamp = service.nextTimestamp();
/*
* Request a read-only view which is in the past based on the
* transaction server's clock. However, there are no commit points
* which cover that timestamp since there are no commit points in
* the database.
*/
final long tx = service.newTx(timestamp - 1);
final TxState txState = service.getTxState(tx);
assertTrue(txState.isReadOnly());
assertTrue(txState.isActive());
} finally {
service.destroy();
}
}
/**
* Verify the behavior of the {@link AbstractTransactionService} when there
* are no commit points and a read-write transaction is requested. You can
* always obtain a read-write transaction, even when there are no commit
* points on the database.
*/
public void test_newTx_nothingCommitted_readWriteTx() {
final MockTransactionService service = newFixture();
try {
/*
* Note: The commit time log is empty.
*/
final long tx = service.newTx(ITx.UNISOLATED);
final TxState txState = service.getTxState(tx);
assertFalse(txState.isReadOnly());
assertTrue(txState.isActive());
} finally {
service.destroy();
}
}
/**
* Verify that you can create a read-only transaction using a timestamp that
* is in the future. A commit point is generated and a read-only tx is
* requested which is beyond that commit point. The returned tx will be
* assigned using nextTimestamp() which is guaranteed to be less than the
* next commit point on the database (which in this case would be the first
* commit point as well).
*/
public void test_newTx_readOnly_timestampInFuture() {
final MockTransactionService service = newFixture();
try {
// request a timestamp.
final long timestamp1 = service.nextTimestamp();
// make that timestamp a valid commit time.
service.notifyCommit(timestamp1);
// try {
// request a timestamp in the future.
final long tx = service.newTx(timestamp1 * 2);
final TxState txState = service.getTxState(tx);
if (log.isInfoEnabled()) {
log.info("ts=" + timestamp1);
log.info("tx=" + tx);
}
assertTrue(txState.isReadOnly());
assertEquals(timestamp1, txState.getReadsOnCommitTime());
// fail("Expecting: "+IllegalStateException.class);
// } catch(IllegalStateException ex) {
// log.info("Ignoring expected exception: "+ex);
// }
} finally {
service.destroy();
}
}
/**
* Unit test verifies that the release time does NOT advance when the
* earliest running transaction terminates but a second transaction is still
* active which reads on the same commit time.
*
* @see https://sourceforge.net/apps/trac/bigdata/ticket/467
*/
public void test_newTx_readOnly_releaseTimeRespectsReadsOnCommitTime()
throws IOException {
final Properties p = new Properties();
// Note: No longer the default. Must be explicitly set.
p.setProperty(AbstractTransactionService.Options.MIN_RELEASE_AGE, "0");
final MockTransactionService service = newFixture(p);
try {
/*
* Verify that the service is not retaining history beyond the last
* commit point.
*/
assertEquals(0L, service.getMinReleaseAge());
final long oldReleaseTime = service.getReleaseTime();
assertEquals(0L,oldReleaseTime);
// this will be the earliest running tx until it completes.
final long tx0 = service.newTx(ITx.UNISOLATED);
final TxState txState0 = service.getTxState(tx0);
// timestamp GT [abs(tx0)] and LT [abs(tx1)].
final long ts = service.nextTimestamp();
assertTrue(ts > Math.abs(tx0));
// this will become the earliest running tx if tx0 completes first.
final long tx1 = service.newTx(ITx.UNISOLATED);
final TxState txState1 = service.getTxState(tx0);
assertTrue(ts < Math.abs(tx1));
// The transactions are reading on the same commit point.
assertEquals(txState0.getReadsOnCommitTime(),
txState1.getReadsOnCommitTime());
// commit tx0
// final long commitTime0 =
service.commit(tx0);
final long newReleaseTime = service.getReleaseTime();
/*
* Verify release time was NOT updated since both transactions are
* reading from the same commit time.
*/
assertEquals(oldReleaseTime, newReleaseTime);
/*
* Try to read from [ts]. This should succeed since the release time
* was not advanced.
*/
service.newTx(ts);
} finally {
service.destroy();
}
}
/**
* Verify that a request for an historical state that is no longer available
* will be rejected.
* <p>
* The test is setup as follows:
*
* <pre>
* +------tx2---------------
* +-----------------tx1---------------+
* +-----------tx0---------+
* +=====================================================+
* +========================
* 0-----+-----+-----+-----+-----+-----+-----+-----+-----+-----+-----+
* tx0 | tx1 | | | tx2 | |
* (0) ts1 (0) ts2 | ts3 (ct0) ts4 |
* ct0 ct1
* rt=0 rt=ct0-1
* </pre>
*
* where tx0, ... are transactions.<br/>
* where ts0, ... are timestamps.<br/>
* where ct0, ... are the commit times for the corresponding tx#.<br/>
* where (...) indicates the commit time on which the transaction is
* reading.<br/>
* where rt# is a releaseTime. The releaseTime and lastCommitTime are
* initially ZERO (0).<br/>
* The long "====" lines above the timeline represent the period during
* which a given commit point is available to a new transaction.<br/>
* The long "---" lines above the timeline represent the life cycle of a
* specific transaction.<br/>
* <p>
* Any transaction which starts before ct1 will see the history going back
* to commitTime=0. This commit time is initially available because there is
* no committed data. It is pinned by tx0 and then by tx1. Once both of
* those transactions complete, that commit time is released (minReleaseAge
* is zero).
* <p>
* When tx1 completes, the release time is advanced (rt=ct0-1).
* <p>
* Any transaction which starts after tx2 will see history back to ct0.
*/
public void test_newTx_readOnly_historyGone() throws IOException {
final Properties p = new Properties();
// Note: No longer the default. Must be explicitly set.
p.setProperty(AbstractTransactionService.Options.MIN_RELEASE_AGE, "0");
final MockTransactionService service = newFixture(p);
try {
/*
* Verify that the service is not retaining history beyond the last
* commit point.
*/
assertEquals(0L, service.getMinReleaseAge());
final long oldReleaseTime = service.getReleaseTime();
assertEquals(0L, oldReleaseTime);
// this will be the earliest running tx until it completes.
final long tx0 = service.newTx(ITx.UNISOLATED);
assertEquals(0L, service.getReadsOnTime(tx0));
// timestamp GT [abs(tx0)] and LT [abs(tx1)].
final long ts1 = service.nextTimestamp();
assertTrue(ts1 > Math.abs(tx0));
// this will become the earliest running tx if tx0 completes first.
final long tx1 = service.newTx(ITx.UNISOLATED);
assertEquals(0L, service.getReadsOnTime(tx1));
// timestamp GT [abs(tx1)] and LT [abs(tx2)].
final long ts2 = service.nextTimestamp();
assertTrue(ts1 < Math.abs(tx1));
assertTrue(ts2 > Math.abs(tx1));
// commit tx0.
final long commitTimeTx0 = service.commit(tx0);
assertTrue(commitTimeTx0 > ts2);
final long newReleaseTime = service.getReleaseTime();
// verify release time was NOT updated.
assertEquals(oldReleaseTime, newReleaseTime);
// After tx0 commits.
final long ts3 = service.nextTimestamp();
assertTrue(ts3 > commitTimeTx0);
/*
* Start another transaction. This should read from the commitTime
* for tx0.
*/
final long tx2 = service.newTx(ITx.UNISOLATED);
// After tx2 starts
final long ts4 = service.nextTimestamp();
assertTrue(ts2 < Math.abs(tx2));
assertTrue(ts4 > Math.abs(tx2));
/*
* Commit tx1. The releaseTime SHOULD be updated since tx1 was the
* earliest running transaction and no remaining transaction reads
* from the same commit time as tx1.
*/
final long commitTimeTx1 = service.commit(tx1);
assertTrue(commitTimeTx1 > commitTimeTx0);
assertTrue(commitTimeTx1 > ts3);
assertTrue(commitTimeTx1 > tx2);
final long newReleaseTime2 = service.getReleaseTime();
// verify release time was updated.
assertNotSame(oldReleaseTime, newReleaseTime2);
/*
* Should have advanced the release time right up to (but LT) the
* commit time on which tx2 is reading, which is the commitTime for
* tx0.
*
* Note: This assumes [minReleaseAge==0].
*/
assertEquals(Math.abs(commitTimeTx0) - 1, newReleaseTime2);
try {
/*
* Try to read from [ts1]. This timestamp was obtain after tx0
* and before tx1. Since [minReleaseAge==0], the history for
* this timestamp was released after both tx0 and tx1 were done.
* Therefore, we should not be able to obtain a transaction for
* this timestamp.
*/
service.newTx(ts1);
fail("Expecting: " + IllegalStateException.class);
} catch (IllegalStateException ex) {
if (log.isInfoEnabled())
log.info("Ignoring expected exception: " + ex);
}
try {
/*
* Try to read from [ts2]. This timestamp was obtain after tx1
* and before tx1 was committed. Since [minReleaseAge==0], the
* history for this timestamp was released after both tx0 and
* tx1 were done. Therefore, we should not be able to obtain a
* transaction for this timestamp.
*/
service.newTx(ts2);
fail("Expecting: " + IllegalStateException.class);
} catch (IllegalStateException ex) {
if (log.isInfoEnabled())
log.info("Ignoring expected exception: " + ex);
}
/*
* This will read on the commit point pinned by tx1, which is
* [commitTimeTx0].
*/
final long tx3 = service.newTx(ts3);
assertEquals(commitTimeTx0, service.getReadsOnTime(tx3));
} finally {
service.destroy();
}
}
/**
* This is a variant on {@link #test_newTx_readOnly_historyGone()} where we
* do not start tx2. In this case, when we end tx1 the release time will
* advance right up to the most recent commit time.
* <p>
* The test is setup as follows:
*
* <pre>
* +-----------------tx1---------------+
* +-----------tx0---------+
* +=====================================================+
* 0-----+-----+-----+-----+-----+-----+-----+-----+-----+-----+-----+
* tx0 | tx1 | | | | |
* (0) ts1 (0) ts2 | ts3 |
* ct0 ct1
* rt=0 rt=now-1
* </pre>
*
* where tx0, ... are transactions.<br/>
* where ts0, ... are timestamps.<br/>
* where ct0, ... are the commit times for the corresponding tx#.<br/>
* where (...) indicates the commit time on which the transaction is
* reading.<br/>
* where rt# is a releaseTime. The releaseTime and lastCommitTime are
* initially ZERO (0).<br/>
* The long "====" lines above the timeline represent the period during
* which a given commit point is available to a new transaction.<br/>
* The long "---" lines above the timeline represent the life cycle of a
* specific transaction.<br/>
* <p>
* Any transaction which starts before ct1 will see the history going back
* to commitTime=0. This commit time is initially available because there is
* no committed data. It is pinned by tx0 and then by tx1. Once both of
* those transactions complete, that commit time is released (minReleaseAge
* is zero).
* <p>
* When tx1 completes, the release time is advanced (rt=now-1).
* <p>
* Any transaction which starts after ct1 will see read on ct1.
*/
public void test_newTx_readOnly_historyGone2() throws IOException {
final Properties p = new Properties();
// Note: No longer the default. Must be explicitly set.
p.setProperty(AbstractTransactionService.Options.MIN_RELEASE_AGE, "0");
final MockTransactionService service = newFixture(p);
try {
/*
* Verify that the service is not retaining history beyond the last
* commit point.
*/
assertEquals(0L, service.getMinReleaseAge());
final long oldReleaseTime = service.getReleaseTime();
assertEquals(0L, oldReleaseTime);
// this will be the earliest running tx until it completes.
final long tx0 = service.newTx(ITx.UNISOLATED);
assertEquals(0L, service.getReadsOnTime(tx0));
// timestamp GT [abs(tx0)] and LT [abs(tx1)].
final long ts1 = service.nextTimestamp();
assertTrue(ts1 > Math.abs(tx0));
// this will become the earliest running tx if tx0 completes first.
final long tx1 = service.newTx(ITx.UNISOLATED);
assertEquals(0L, service.getReadsOnTime(tx1));
// timestamp GT [abs(tx1)] and LT [abs(tx2)].
final long ts2 = service.nextTimestamp();
assertTrue(ts1 < Math.abs(tx1));
assertTrue(ts2 > Math.abs(tx1));
// commit tx0.
final long commitTimeTx0 = service.commit(tx0);
assertTrue(commitTimeTx0 > ts2);
final long newReleaseTime = service.getReleaseTime();
// verify release time was NOT updated.
assertEquals(oldReleaseTime, newReleaseTime);
// After tx0 commits.
final long ts3 = service.nextTimestamp();
assertTrue(ts3 > commitTimeTx0);
/*
* Commit tx1. The releaseTime SHOULD be updated since tx1 was the
* earliest running transaction and no remaining transaction reads
* from the same commit time as tx1.
*/
final long commitTimeTx1 = service.commit(tx1);
assertTrue(commitTimeTx1 > commitTimeTx0);
assertTrue(commitTimeTx1 > ts3);
final long ts4 = service.nextTimestamp();
assertTrue(ts4 > commitTimeTx1);
final long newReleaseTime2 = service.getReleaseTime();
// verify release time was updated.
assertNotSame(oldReleaseTime, newReleaseTime2);
/*
* Should have advanced the release time right up to (but LT) the
* commitTime for tx1.
*/
assertEquals(Math.abs(commitTimeTx1) - 1, newReleaseTime2);
try {
/*
* Try to read from [ts1]. This timestamp was obtain after tx0
* and before tx1. Since [minReleaseAge==0], the history for
* this timestamp was released after both tx0 and tx1 were done.
* Therefore, we should not be able to obtain a transaction for
* this timestamp.
*/
service.newTx(ts1);
fail("Expecting: " + IllegalStateException.class);
} catch (IllegalStateException ex) {
log.info("Ignoring expected exception: " + ex);
}
try {
/*
* Try to read from [ts2]. This timestamp was obtain after tx1
* and before tx1 was committed. Since [minReleaseAge==0], the
* history for this timestamp was released after both tx0 and
* tx1 were done. Therefore, we should not be able to obtain a
* transaction for this timestamp.
*/
service.newTx(ts2);
fail("Expecting: " + IllegalStateException.class);
} catch (IllegalStateException ex) {
log.info("Ignoring expected exception: " + ex);
}
try {
/*
* Try to read from [ts3]. This timestamp was obtain before tx1
* committed. Since [minReleaseAge==0], the history for this
* timestamp was released after both tx0 and tx1 were done.
* Therefore, we should not be able to obtain a transaction for
* this timestamp.
*/
service.newTx(ts3);
fail("Expecting: " + IllegalStateException.class);
} catch (IllegalStateException ex) {
log.info("Ignoring expected exception: " + ex);
}
/*
* Start transaction. This will read on the commitTime for tx1.
*/
final long tx3 = service.newTx(ts4);
assertEquals(commitTimeTx1, service.getReadsOnTime(tx3));
} finally {
service.destroy();
}
}
/**
* Test verifies the advance of the release time when the earliest running
* transaction completes. This version focuses on when there are no active
* transactions once the earliest transaction completes. In this case the
* [lastCommitTime] is the exclusive upper bound for the new releaseTime.
*
* @throws IOException
*/
public void test_updateReleaseTime_noTxRemaining() throws IOException {
final MockTransactionService service = newFixture();
try {
/*
* Note: We have to force a commit time on the log.
*
* Note that if [lastCommitTime==0] the releaseTime can not be
* advanced if there are also no running transactions.
*/
service.notifyCommit(service.nextTimestamp());
assertEquals(0L, service.getReleaseTime());
final long tx0 = service.newTx(ITx.UNISOLATED);
final long tx1 = service.newTx(ITx.UNISOLATED);
// unchanged.
assertEquals(0L, service.getReleaseTime());
// Abort the 2nd tx.
service.abort(tx1);
// unchanged.
assertEquals(0L, service.getReleaseTime());
// terminate the 1st tx.
service.abort(tx0);
// verify that releaseTime was updated.
final long releaseTime = service.getReleaseTime();
final long lastCommitTime = service.getLastCommitTime();
if (log.isInfoEnabled()) {
log.info("tx0=" + tx0);
log.info("tx1=" + tx1);
log.info("releaseTime=" + releaseTime);
log.info("lastCommitTime=" + lastCommitTime);
}
assertNotSame(0L, releaseTime);
/*
* Note: The release time MUST NOT be advanced to the last commit
* time!!! That would cause ALL commit points to be released.
*
* Note: Both tx1 and tx0 are GT lastCommitTime so we can not test
* against those here, but see the other test.
*/
assertTrue(releaseTime < lastCommitTime);
} finally {
service.destroy();
}
}
/**
* A unit test of advancing the last release time for the case where there
* are still active transactions running once the earliest active
* transaction commits.
*
* @throws IOException
*/
public void test_updateReleaseTime_otherTxStillActive() throws IOException {
final MockTransactionService service = newFixture();
try {
// /*
// * Note: We have to force a commit time on the log since we are not
// * really integrated with a database and if [lastCommitTime==0] the
// * releaseTime can not be advanced if there are also no running
// * transactions.
// */
// service.notifyCommit(service.nextTimestamp());
// original last commit time is zero.
assertEquals(0L, service.getLastCommitTime());
// original release time is zero.
assertEquals(0L, service.getReleaseTime());
final long tx0 = service.newTx(ITx.UNISOLATED);
final long tx1 = service.newTx(ITx.UNISOLATED);
final long tx2 = service.newTx(ITx.UNISOLATED);
// unchanged.
assertEquals(0L, service.getReleaseTime());
// Commit the 2nd tx
service.commit(tx1);
// unchanged.
assertEquals(0L, service.getReleaseTime());
// terminate the 1st tx.
service.abort(tx0);
{
/*
* Verify that releaseTime was NOT updated yet. The original
* commit time is still pinned by [tx2].
*/
assertEquals(0L, service.getReleaseTime());
/*
* However, the lastCommitTime SHOULD have been updated.
*/
assertTrue(service.getLastCommitTime() > 0);
}
/*
* Finally, commit the last tx.
*
* Note: This should cause the release time to be advanced.
*/
final long ct2 = service.commit(tx2);
final long releaseTime = service.getReleaseTime();
final long lastCommitTime = service.getLastCommitTime();
if (log.isInfoEnabled()) {
log.info("tx0 =" + tx0);
log.info("tx1 =" + tx1);
log.info("tx2 =" + tx2);
log.info("ct2 = " + ct2);
log.info("releaseTime = " + releaseTime);
log.info("lastCommitTime= " + lastCommitTime);
}
// Verify release time was updated.
assertNotSame(0L, releaseTime);
// Verify the expected release time.
assertEquals(ct2 - 1, releaseTime);
/*
* Note: The release time MUST NOT be advanced to the last commit
* time!!!
*
* That would cause ALL commit points to be released.
*/
assertTrue(releaseTime < lastCommitTime);
// // releaseTime GTE [tx0].
// assertTrue(releaseTime >= Math.abs(tx0));
//
// // releaseTime is LT [tx2].
// assertTrue(releaseTime < Math.abs(tx2));
} finally {
service.destroy();
}
}
/**
* Create a read-only transaction, commit it, and then attempt to re-commit
* it and to abort it - those operations should fail with an
* {@link IllegalStateException}.
*
* @throws IOException
*/
public void test_newTx_readOnly_txComplete_postConditions() throws IOException {
final MockTransactionService service = newFixture();
try {
// make this a valid commit time.
service.notifyCommit(10);
final long tx = service.newTx(10);
final TxState txState = service.getTxState(tx);
service.commit(tx);
assertTrue(txState.isCommitted());
assertTrue(txState.isComplete());
try {
service.commit(tx);
fail("Expecting: "+IllegalStateException.class);
} catch(IllegalStateException ex) {
log.info("Ignoring expected exception: "+ex);
}
try {
service.abort(tx);
fail("Expecting: "+IllegalStateException.class);
} catch(IllegalStateException ex) {
log.info("Ignoring expected exception: "+ex);
}
// Verify unchanged by failed abort.
assertTrue(txState.isCommitted());
assertTrue(txState.isComplete());
} finally {
service.destroy();
}
}
/**
* Verifies that we can shutdown() the service when there are no
* active transactions.
*/
public void test_shutdown_nothingRunning() {
final MockTransactionService service = newFixture();
try {
service.shutdown();
} finally {
service.destroy();
}
}
/**
* Test that the service will wait for a read-write tx to commit.
*
* @throws InterruptedException
*/
public void test_shutdown_waitsForReadWriteTx_commits()
throws InterruptedException {
final MockTransactionService service = newFixture();
try {
final long tx = service.newTx(ITx.UNISOLATED);
final TxState txState = service.getTxState(tx);
assertFalse(txState.isReadOnly());
assertTrue(txState.isActive());
final Thread t = new Thread() {
public void run() {
service.shutdown();
// Tx is still running.
assertTrue(txState.isActive());
// Still visible & active.
assertTrue(txState == service.getTxState(tx));
}
};
t.setDaemon(true);
t.start();
service.awaitRunState( TxServiceRunState.Shutdown);
// Tx is still running.
assertTrue(txState.isActive());
// Still visible & active.
assertTrue(txState == service.getTxState(tx));
// commit the running tx.
service.commit(tx);
service.awaitRunState( TxServiceRunState.Halted);
// Tx is done.
assertFalse(txState.isActive());
assertTrue(txState.isCommitted());
assertTrue(txState.isComplete());
} finally {
service.destroy();
}
}
/**
* Test that the service will wait for a read-write tx to abort.
*
* @throws InterruptedException
*/
public void test_shutdown_waitsForReadWriteTx_aborts()
throws InterruptedException {
final MockTransactionService service = newFixture();
try {
final long tx = service.newTx(ITx.UNISOLATED);
final TxState txState = service.getTxState(tx);
assertFalse(txState.isReadOnly());
assertTrue(txState.isActive());
final Thread t = new Thread() {
public void run() {
service.shutdown();
// Tx is still running.
assertTrue(txState.isActive());
// Still visible & active.
assertTrue(txState == service.getTxState(tx));
}
};
t.setDaemon(true);
t.start();
service.awaitRunState( TxServiceRunState.Shutdown);
// abort the running tx.
service.abort(tx);
service.awaitRunState( TxServiceRunState.Halted);
// Tx is done.
assertFalse(txState.isActive());
assertFalse(txState.isCommitted());
assertTrue(txState.isComplete());
} finally {
service.destroy();
}
}
/**
* Test that shutdown() does not permit new tx to start (a variety of things
* are not permitted during shutdown).
*
* @throws InterruptedException
*/
public void test_shutdown_newTxNotAllowed() throws InterruptedException {
final MockTransactionService service = newFixture();
try {
final long tx = service.newTx(ITx.UNISOLATED);
final TxState txState = service.getTxState(tx);
assertFalse(txState.isReadOnly());
assertTrue(txState.isActive());
final Thread t = new Thread() {
public void run() {
service.shutdown();
// Still running.
assertTrue(txState.isActive());
}
};
t.setDaemon(true);
t.start();
// verify service is shutting down.
service.awaitRunState( TxServiceRunState.Shutdown);
// Still running.
assertTrue(txState.isActive());
// verify newTx() is disallowed during shutdown.
try {
service.newTx(ITx.UNISOLATED);
fail("Expecting: " + IllegalStateException.class);
} catch (IllegalStateException ex) {
if (log.isInfoEnabled())
log.info("Ignoring expected exception: " + ex);
}
// one tx left.
assertEquals(1,service.getActiveCount());
// abort the last running tx.
service.abort(tx);
// done.
assertFalse(txState.isActive());
// no tx left.
assertEquals(0,service.getActiveCount());
// wait until the service halts.
service.awaitRunState(TxServiceRunState.Halted);
} finally {
service.destroy();
}
}
/**
* Test that the service will wait for a read-only tx to commit.
*/
public void test_shutdown_waitsForReadOnlyTx_commits()
throws InterruptedException {
final MockTransactionService service = newFixture();
try {
final long tx = service.newTx(ITx.UNISOLATED);
final TxState txState = service.getTxState(tx);
assertFalse(txState.isReadOnly());
assertTrue(txState.isActive());
final Thread t = new Thread() {
public void run() {
service.shutdown();
// Still running.
assertTrue(txState.isActive());
}
};
t.setDaemon(true);
t.start();
service.awaitRunState( TxServiceRunState.Shutdown);
// Tx is still running.
assertTrue(txState.isActive());
// Still visible & active.
assertTrue(txState == service.getTxState(tx));
// commit the running tx.
service.commit(tx);
service.awaitRunState( TxServiceRunState.Halted);
// Tx is done.
assertFalse(txState.isActive());
assertTrue(txState.isCommitted());
assertTrue(txState.isComplete());
} finally {
service.destroy();
}
}
/**
* Test that the service will wait for a read-only tx to abort.
*/
public void test_shutdown_waitsForReadOnlyTx_aborts()
throws InterruptedException {
final MockTransactionService service = newFixture();
try {
final long tx = service.newTx(ITx.UNISOLATED);
final TxState txState = service.getTxState(tx);
assertFalse(txState.isReadOnly());
assertTrue(txState.isActive());
final Thread t = new Thread() {
public void run() {
service.shutdown();
// Still running.
assertTrue(txState.isActive());
}
};
t.setDaemon(true);
t.start();
service.awaitRunState( TxServiceRunState.Shutdown);
// Tx is still running.
assertTrue(txState.isActive());
// Still visible & active.
assertTrue(txState == service.getTxState(tx));
// abort the running tx.
service.abort(tx);
// Tx is done.
assertFalse(txState.isActive());
assertTrue(txState.isAborted());
assertFalse(txState.isCommitted());
assertTrue(txState.isComplete());
service.awaitRunState( TxServiceRunState.Halted);
} finally {
service.destroy();
}
}
/**
* Test that shutdown() may be interrupted while waiting for a tx to
* complete and that it will convert to shutdownNow() which does not wait.
*
* @throws InterruptedException
*/
public void test_shutdown_interrupted() throws InterruptedException {
final MockTransactionService service = newFixture();
try {
final long tx = service.newTx(ITx.UNISOLATED);
final TxState txState = service.getTxState(tx);
assertFalse(txState.isReadOnly());
assertTrue(txState.isActive());
final Thread t = new Thread() {
public void run() {
service.shutdown();
// Verify still active.
assertTrue(txState.isActive());
}
};
t.setDaemon(true);
t.start();
service.awaitRunState( TxServiceRunState.Shutdown);
// Verify still active.
assertTrue(txState.isActive());
// interrupt the thread running shutdown().
t.interrupt();
service.awaitRunState( TxServiceRunState.Halted);
// Tx is done (was terminated when shutdownNow() ran).
assertFalse(txState.isActive());
assertTrue(txState.isAborted());
assertFalse(txState.isCommitted());
assertTrue(txState.isComplete());
} finally {
service.destroy();
}
}
/**
* Test for {@link AbstractTransactionService#abortAllTx()}.
*/
public void test_abortAll() throws InterruptedException {
final MockTransactionService service = newFixture();
try {
// read-write tx.
final long tx0 = service.newTx(ITx.UNISOLATED);
final TxState txState0 = service.getTxState(tx0);
assertFalse(txState0.isReadOnly());
assertTrue(txState0.isActive());
assertEquals(tx0, service.getEarliestActiveTx().getStartTimestamp());
// read-only tx.
final long tx1 = service.newTx(ITx.READ_COMMITTED);
final TxState txState1 = service.getTxState(tx1);
assertTrue(txState1.isReadOnly());
assertTrue(txState1.isActive());
// unchanged.
assertEquals(tx0, service.getEarliestActiveTx().getStartTimestamp());
// abort the running transactions.
service.abortAllTx();
// Tx is done.
assertFalse(txState0.isActive());
assertTrue(txState0.isAborted());
assertFalse(txState0.isCommitted());
assertTrue(txState0.isComplete());
// Tx is done.
assertFalse(txState1.isActive());
assertTrue(txState1.isAborted());
assertFalse(txState1.isCommitted());
assertTrue(txState1.isComplete());
// Nothing active.
assertNull(service.getEarliestActiveTx());
/*
* Verify we can start new transactions.
*/
final long tx2 = service.newTx(ITx.UNISOLATED);
final TxState txState2 = service.getTxState(tx2);
assertFalse(txState2.isReadOnly());
assertTrue(txState2.isActive());
// earliest active tx was updated.
assertEquals(tx2, service.getEarliestActiveTx().getStartTimestamp());
} finally {
service.destroy();
}
}
}