package org.jboss.cache.transaction;
import EDU.oswego.cs.dl.util.concurrent.Latch;
import junit.framework.AssertionFailedError;
import junit.framework.Test;
import junit.framework.TestCase;
import junit.framework.TestSuite;
import org.jboss.cache.DummyTransactionManagerLookup;
import org.jboss.cache.Fqn;
import org.jboss.cache.TreeCache;
import org.jboss.cache.lock.IsolationLevel;
import org.jboss.cache.lock.TimeoutException;
import javax.transaction.NotSupportedException;
import javax.transaction.SystemException;
import javax.transaction.Transaction;
/**
* Tests READ_COMMITED isolation level.
*
* @author <a href="mailto:ovidiu@jboss.org">Ovidiu Feodorov</a>
*
* @version $Id: IsolationLevelReadCommittedTest.java 3014 2006-11-22 18:42:24Z bstansberry $
*/
public class IsolationLevelReadCommittedTest extends TestCase
{
private TreeCache cache = null;
private final Fqn FQN = Fqn.fromString("/a/b/c");
private final Fqn PARENT_FQN = Fqn.fromString("/a/b");
private final String KEY = "key";
private final String VALUE = "value";
private volatile boolean writerFailed;
private volatile boolean readerFailed;
private volatile AssertionFailedError writerError;
private volatile AssertionFailedError readerError;
protected void setUp() throws Exception
{
super.setUp();
writerFailed = false;
readerFailed = false;
writerError = null;
readerError = null;
cache = new TreeCache();
cache.setCacheMode(TreeCache.LOCAL);
cache.setIsolationLevel(IsolationLevel.READ_COMMITTED);
cache.setTransactionManagerLookupClass(DummyTransactionManagerLookup.class.getName());
cache.setLockAcquisitionTimeout(1000);
cache.startService();
}
protected void tearDown() throws Exception
{
super.tearDown();
cache.stopService();
cache.destroyService();
cache=null;
}
/**
* The test starts a reader and writer thread and coordinates access to the cache so the reader
* reads after the writer changes a node in a transaction, but before roll back. The reader
* should never read anything else than the original value (uncommitted values should be
* isolated)
*/
public void testReadCommitted() throws Exception
{
final Latch readerCanRead = new Latch();
final Latch readerDone = new Latch();
final Latch writerCanWrite = new Latch();
final Latch writerCanRollback = new Latch();
final Latch writerDone = new Latch();
cache.put(FQN, KEY, VALUE);
assertEquals(VALUE, cache.get(FQN, KEY));
// start a reader thread; need a transaction so its initial
// read lock is maintained while the writer does its work
Thread readerThread = new Thread(new Runnable()
{
public void run()
{
Transaction tx = null;
try
{
tx = startTransaction();
// Read the value, thus acquiring a read lock on FQN
assertEquals(VALUE, cache.get(FQN, KEY));
writerCanWrite.release();
// wait until the writer thread changes the value in a transaction, but it did not
// yet commit or roll back.
readerCanRead.acquire();
try
{
// I shouldn't be able to see the "dirty" value
assertEquals("thread w/ read lock " +
"can see subsequent uncommitted changes",
VALUE, cache.get(FQN, KEY));
}
catch (TimeoutException good)
{
// this is what should happen due to writer's WL
}
// let the writer know it can rollback
writerCanRollback.release();
// I should still be able to see the "clean" value
assertEquals(VALUE, cache.get(FQN, KEY));
}
catch (AssertionFailedError e)
{
readerError = e;
}
catch(Throwable t)
{
t.printStackTrace();
readerFailed = true;
}
finally
{
System.out.println("reader thread exits");
if (tx != null)
try { tx.commit(); } catch (Exception e) {}
writerCanWrite.release();
writerCanRollback.release();
readerDone.release();
}
}
}, "READER");
readerThread.start();
// start a writer thread and a transaction
Thread writerThread = new Thread(new Runnable()
{
public void run()
{
try
{
// wait until the reader thread reads and allows me to start
writerCanWrite.attempt(3000);
Transaction tx2 = startTransaction();
// change VALUE in a transaction
cache.put(FQN, KEY, "this-shouldnt-be-visible");
// notify the reading thread
readerCanRead.release();
// wait until the reader thread reads and allows me to rollback or until I timeout
writerCanRollback.attempt(3000);
System.out.println("rolling back");
tx2.rollback();
}
catch (AssertionFailedError e)
{
writerError = e;
}
catch(Throwable t)
{
t.printStackTrace();
writerFailed = true;
}
finally
{
System.out.println("writer thread exits");
readerCanRead.release();
writerDone.release();
}
}
}, "WRITER");
writerThread.start();
// wait for both threads to finish
readerDone.acquire();
writerDone.acquire();
// If any assertion failed, throw on the AssertionFailedError
if (readerError != null)
{
throw readerError;
}
if (writerError != null)
{
throw writerError;
}
if (readerFailed)
{
fail("The reader thread exited incorrectly. Watch the log for previous stack traces");
}
if (writerFailed)
{
fail("The writer thread exited incorrectly. Watch the log for previous stack traces");
}
}
/**
* Test creates a cache node then starts a separate thread that removes
* the node inside a tx. Test confirms that the removal cannot be seen
* before the test commits.
*
* @throws Exception
*/
public void testNodeRemoved() throws Exception
{
final Latch readerCanRead = new Latch();
final Latch readerDone = new Latch();
final Latch writerDone = new Latch();
cache.put(FQN, KEY, VALUE);
assertEquals(VALUE, cache.get(FQN, KEY));
// start a writer thread and a transaction
Thread writerThread = new Thread(new Runnable()
{
public void run()
{
try
{
Transaction tx = startTransaction();
// change VALUE in a transaction
cache.remove(PARENT_FQN);
// notify the reading thread
readerCanRead.release();
readerDone.acquire();
tx.commit();
}
catch (AssertionFailedError e)
{
writerError = e;
}
catch(Throwable t)
{
t.printStackTrace();
writerFailed = true;
}
finally
{
System.out.println("writer thread exits");
readerCanRead.release();
writerDone.release();
}
}
}, "WRITER");
writerThread.start();
try
{
// wait until the writer thread changes the value in a transaction,
// but it did not yet commit or roll back.
readerCanRead.acquire();
// I shouldn't be able to see the "dirty" value
assertEquals("2nd thread cannot see uncommitted changes",
VALUE, cache.get(FQN, KEY));
}
catch (TimeoutException t)
{
// ignore, this is good
}
finally
{
System.out.println("reader thread exits");
readerDone.release();
}
// wait for the writer to finish
writerDone.acquire();
assertNull("Node was removed", cache.get(FQN));
// If any assertion failed, throw on the AssertionFailedError
if (writerError != null)
{
throw writerError;
}
if (writerFailed)
{
fail("The writer thread exited incorrectly. Watch the log for previous stack traces");
}
}
private Transaction startTransaction() throws SystemException, NotSupportedException
{
DummyTransactionManager mgr=DummyTransactionManager.getInstance();
mgr.begin();
return mgr.getTransaction();
}
public static Test suite() {
return new TestSuite(IsolationLevelReadCommittedTest.class);
}
}