/*
*
* JBoss, the OpenSource J2EE webOS
*
* Distributable under LGPL license.
* See terms of license at gnu.org.
*/
package org.jboss.cache.transaction;
import junit.framework.Test;
import junit.framework.TestCase;
import junit.framework.TestSuite;
import org.jboss.cache.CacheException;
import org.jboss.cache.DataNode;
import org.jboss.cache.Fqn;
import org.jboss.cache.GlobalTransaction;
import org.jboss.cache.TreeCache;
import org.jboss.cache.lock.IdentityLock;
import org.jboss.cache.lock.IsolationLevel;
import javax.naming.Context;
import javax.naming.InitialContext;
import javax.transaction.HeuristicMixedException;
import javax.transaction.HeuristicRollbackException;
import javax.transaction.NotSupportedException;
import javax.transaction.RollbackException;
import javax.transaction.SystemException;
import javax.transaction.Transaction;
import javax.transaction.UserTransaction;
import java.util.HashMap;
import java.util.Map;
import java.util.Properties;
import java.util.Set;
/**
* Tests transactional access to a local TreeCache.
* Note: we use DummpyTranasctionManager to replace jta
*
* @version $Id: TransactionTest.java 3628 2007-03-05 19:15:56Z msurtani $
*/
public class TransactionTest extends TestCase {
TreeCache cache=null;
UserTransaction tx=null;
Properties p=null;
String old_factory=null;
final String FACTORY="org.jboss.cache.transaction.DummyContextFactory";
Exception thread_ex;
public TransactionTest(String name) {
super(name);
}
public void setUp() throws Exception {
super.setUp();
old_factory=System.getProperty(Context.INITIAL_CONTEXT_FACTORY);
System.setProperty(Context.INITIAL_CONTEXT_FACTORY, FACTORY);
DummyTransactionManager.getInstance();
if(p == null) {
p=new Properties();
p.put(Context.INITIAL_CONTEXT_FACTORY, "org.jboss.cache.transaction.DummyContextFactory");
}
tx=(UserTransaction)new InitialContext(p).lookup("UserTransaction");
cache=new TreeCache("test", null, 10000);
cache.setTransactionManagerLookupClass("org.jboss.cache.DummyTransactionManagerLookup");
cache.setIsolationLevel(IsolationLevel.SERIALIZABLE);
cache.setLockParentForChildInsertRemove(true); // these tests are written with this assumption in mind
cache.createService();
cache.startService();
thread_ex=null;
}
public void tearDown() throws Exception {
super.tearDown();
if(cache != null)
cache.stopService();
// BW. kind of a hack to destroy jndi binding and thread local tx before next run.
DummyTransactionManager.destroy();
if(old_factory != null) {
System.setProperty(Context.INITIAL_CONTEXT_FACTORY, old_factory);
old_factory=null;
}
if(tx != null) {
try {
tx.rollback();
}
catch(Throwable t) {
}
tx=null;
}
}
public void testPutTx() {
try {
tx.begin();
cache.put("/a/b/c", "age", new Integer(38));
// the tx interceptor should know that we're in the same tx.
assertEquals(new Integer(38), cache.get("/a/b/c", "age"));
cache.put("/a/b/c", "age", new Integer(39));
tx.commit();
// This test is done outside the TX, it wouldn't work if someone else
// modified "age". This works because we're the only TX running.
assertEquals(new Integer(39), cache.get("/a/b/c", "age"));
}
catch(Throwable t) {
fail(t.toString());
}
}
public void testRollbackTx1() {
try {
tx.begin();
cache.put("/a/b/c", "age", new Integer(38));
cache.put("/a/b/c", "age", new Integer(39));
tx.rollback();
// This test is done outside the TX, it wouldn't work if someone else
// modified "age". This works because we're the only TX running.
assertNull(cache.get("/a/b/c", "age"));
}
catch(Throwable t) {
t.printStackTrace();
fail(t.toString());
}
}
public void testGetAfterRemovalRollback() throws Exception
{
cache.put("/a/b", null);
assertTrue(cache.exists("/a/b"));
tx.begin();
cache.remove("/a/b");
assertFalse(cache.exists("/a/b"));
tx.rollback();
assertTrue(cache.exists("/a/b"));
assertEquals(0, cache.getNumberOfLocksHeld());
// new tx in new thread
Thread th = new Thread()
{
public void run()
{
try
{
cache.getTransactionManager().begin();
assertNotNull(cache.get("/a/b"));
cache.getTransactionManager().rollback();
}
catch (Exception e)
{
e.printStackTrace();
fail("Caught exception");
}
}
};
th.start();
th.join();
assertEquals(0, cache.getNumberOfLocksHeld());
}
public void testRollbackTx2() {
try {
tx.begin();
cache.put("/a/b/c", "age", new Integer(38));
cache.remove("/a/b/c", "age");
tx.rollback();
// This test is done outside the TX, it wouldn't work if someone else
// modified "age". This works because we're the only TX running.
assertNull(cache.get("/a/b/c", "age"));
}
catch(Throwable t) {
t.printStackTrace();
fail(t.toString());
}
}
public void testRollbackTx2a() {
try {
System.out.println("locks " + cache.printLockInfo());
cache.put("/a/b/c", "age", new Integer(38));
System.out.println("locks " + cache.printLockInfo());
tx.begin();
cache.remove("/a/b/c", "age");
tx.rollback();
// This test is done outside the TX, it wouldn't work if someone else
// modified "age". This works because we're the only TX running.
assertEquals(new Integer(38), cache.get("/a/b/c", "age"));
}
catch(Throwable t) {
t.printStackTrace();
fail(t.toString());
}
}
public void testRollbackTx3() {
try {
java.util.Map map1=new java.util.HashMap();
map1.put("age", new Integer(38));
java.util.Map map2=new java.util.HashMap();
map2.put("age", new Integer(39));
tx.begin();
cache.put("/a/b/c", map1);
cache.put("/a/b/c", map2);
tx.rollback();
// This test is done outside the TX, it wouldn't work if someone else
// modified "age". This works because we're the only TX running.
assertNull(cache.get("/a/b/c", "age"));
}
catch(Throwable t) {
t.printStackTrace();
fail(t.toString());
}
}
public void testRollbackTx4() {
try {
Map map=new HashMap();
map.put("age", new Integer(38));
tx.begin();
cache.put("/a/b/c", map);
cache.remove("/a/b/c");
tx.rollback();
// This test is done outside the TX, it wouldn't work if someone else
// modified "age". This works because we're the only TX running.
assertNull(cache.get("/a/b/c", "age"));
}
catch(Throwable t) {
t.printStackTrace();
fail(t.toString());
}
}
public void testNodeCreationRollback() {
try {
tx.begin();
System.out.println("initial state:\n" + cache);
cache.put("/bela/ban", null);
System.out.println("after put():\n" + cache);
tx.rollback();
System.out.println("after rollback():\n" + cache);
assertNull("node should be not existent", cache.get("/bela/ban"));
}
catch(Throwable t) {
t.printStackTrace();
fail(t.toString());
}
}
public void testNodeCreationRollback2() {
try {
cache.put("/bela/ban", null);
tx.begin();
cache.put("/bela/ban/michelle", null);
tx.rollback();
assertNotNull("node should be not null", cache.get("/bela/ban"));
assertNull("node should be not existent", cache.get("/bela/ban/michelle"));
}
catch(Throwable t) {
t.printStackTrace();
fail(t.toString());
}
}
public void testNodeDeletionRollback() {
try {
cache.put("/a/b/c", null);
tx.begin();
cache.remove("/a/b/c");
assertNull(cache.get("/a/b/c"));
cache.remove("/a/b");
assertNull(cache.get("/a/b"));
cache.remove("/a");
assertNull(cache.get("/a"));
tx.rollback();
assertNotNull(cache.get("/a/b/c"));
assertNotNull(cache.get("/a/b"));
assertNotNull(cache.get("/a"));
}
catch(Throwable t) {
t.printStackTrace();
fail(t.toString());
}
}
public void testNodeDeletionRollback2() {
try {
cache.put("/a/b/c", null);
cache.put("/a/b/c1", null);
cache.put("/a/b/c2", null);
tx.begin();
cache.remove("/a");
assertNull(cache.get("/a/b/c"));
assertNull(cache.get("/a/b/c1"));
assertNull(cache.get("/a/b/c2"));
assertNull(cache.get("/a/b"));
assertNull(cache.get("/a"));
Set children=cache.getChildrenNames("/a/b");
assertNull(children);
children=cache.getChildrenNames("/a");
assertNull(children);
tx.rollback();
assertNotNull(cache.get("/a"));
assertNotNull(cache.get("/a/b"));
assertNotNull(cache.get("/a/b/c"));
assertNotNull(cache.get("/a/b/c1"));
assertNotNull(cache.get("/a/b/c2"));
children=cache.getChildrenNames("/a/b");
assertEquals(3, children.size());
}
catch(Throwable t) {
t.printStackTrace();
fail(t.toString());
}
}
public void testNodeCreation() throws Exception {
GlobalTransaction gtx;
cache.put("/a/b", null);
tx.begin();
gtx=cache.getCurrentTransaction();
cache.put("/a/b/c", null);
assertLocked(gtx, "/a", false);
assertLocked(gtx, "/a/b", true);
assertLocked(gtx, "/a/b/c", true);
System.out.println("locks: " + cache.printLockInfo());
}
public void testNodeCreation2() throws Exception {
GlobalTransaction gtx;
tx.begin();
gtx=cache.getCurrentTransaction();
cache.put("/a/b/c", null);
assertLocked(gtx, "/a", true);
assertLocked(gtx, "/a/b", true);
assertLocked(gtx, "/a/b/c", true);
System.out.println("locks: " + cache.printLockInfo());
}
public void testNodeRemoval() {
GlobalTransaction gtx;
try {
cache.put("/a/b/c", null);
tx.begin();
gtx=cache.getCurrentTransaction();
cache.remove("/a/b/c"); // need to remove the node, not just the data in the node.
assertLocked(gtx, "/a", false);
assertLocked(gtx, "/a/b", true);
assertLocked(gtx, "/a/b/c", true);
System.out.println("locks: " + cache.printLockInfo());
}
catch(Throwable t) {
t.printStackTrace();
fail(t.toString());
}
}
public void testNodeRemoval2() {
GlobalTransaction gtx;
try {
cache.put("/a/b/c", null);
tx.begin();
gtx=cache.getCurrentTransaction();
cache.remove("/a/b"); // need to remove the node, not just the data in the node.
assertLocked(gtx, "/a", true);
assertLocked(gtx, "/a/b", true);
assertLocked(gtx, "/a/b/c", true);
System.out.println("locks: " + cache.printLockInfo());
}
catch(Throwable t) {
t.printStackTrace();
fail(t.toString());
}
}
public void testIntermediateNodeCreationOnWrite() throws Exception
{
cache.put("/a", null);
tx.begin();
cache.put("/a/b/c", null);
// expecting WLs on /a, /a/b and /a/b/c.
GlobalTransaction gtx=cache.getCurrentTransaction();
assertLocked(gtx, "/a", true);
assertLocked(gtx, "/a/b", true);
assertLocked(gtx, "/a/b/c", true);
tx.rollback();
}
public void testIntermediateNodeCreationOnRead() throws Exception
{
cache.put("/a", null);
tx.begin();
cache.get("/a/b/c");
// expecting RLs on /, /a
// /a/b, /a/b/c should NOT be created!
GlobalTransaction gtx=cache.getCurrentTransaction();
assertLocked(gtx, "/", false);
assertLocked(gtx, "/a", false);
assertNull("/a/b should not exist", cache.peek(Fqn.fromString("/a/b")));
assertNull("/a/b/c should not exist", cache.peek(Fqn.fromString("/a/b/c")));
tx.rollback();
assertNull("/a/b should not exist", cache.peek(Fqn.fromString("/a/b")));
assertNull("/a/b/c should not exist", cache.peek(Fqn.fromString("/a/b/c")));
}
public void testIntermediateNodeCreationOnRemove() throws Exception
{
cache.put("/a", null);
tx.begin();
cache.remove("/a/b/c");
// expecting RLs on /, /a
// /a/b, /a/b/c should NOT be created!
GlobalTransaction gtx=cache.getCurrentTransaction();
assertLocked(gtx, "/", false);
assertLocked(gtx, "/a", false);
assertNull("/a/b should not exist", cache.peek(Fqn.fromString("/a/b")));
assertNull("/a/b/c should not exist", cache.peek(Fqn.fromString("/a/b/c")));
tx.rollback();
assertNull("/a/b should not exist", cache.peek(Fqn.fromString("/a/b")));
assertNull("/a/b/c should not exist", cache.peek(Fqn.fromString("/a/b/c")));
}
public void testNodeDeletionRollback3() throws Exception{
GlobalTransaction gtx;
cache.put("/a/b/c1", null);
tx.begin();
gtx=cache.getCurrentTransaction();
cache.put("/a/b/c1", null);
assertLocked(gtx, "/a", false);
assertLocked(gtx, "/a/b", false);
assertLocked(gtx, "/a/b/c1", true);
cache.put("/a/b/c2", null);
assertLocked(gtx, "/a/b", true);
assertLocked(gtx, "/a/b/c2", true);
cache.put("/a/b/c3", null);
cache.put("/a/b/c1/one", null);
assertLocked(gtx, "/a/b/c1", true);
assertLocked(gtx, "/a/b/c1/one", true);
cache.put("/a/b/c1/two", null);
cache.put("/a/b/c1/one/1", null);
assertLocked(gtx, "/a/b/c1", true);
assertLocked(gtx, "/a/b/c1/one", true);
assertLocked(gtx, "/a/b/c1/one/1", true);
cache.put("/a/b/c1/two/2/3/4", null);
assertLocked(gtx, "/a/b/c1", true);
assertLocked(gtx, "/a/b/c1/two", true);
assertLocked(gtx, "/a/b/c1/two/2", true);
assertLocked(gtx, "/a/b/c1/two/2/3", true);
assertLocked(gtx, "/a/b/c1/two/2/3/4", true);
System.out.println("locks: " + cache.printLockInfo());
cache.remove("/a/b");
tx.rollback();
assertTrue(cache.getChildrenNames("/a/b/c1").isEmpty());
Set cn = cache.getChildrenNames("/a/b");
assertEquals(1, cn.size());
assertEquals("c1", cn.iterator().next());
}
public void testDoubleLocks() throws Exception{
tx.begin();
GlobalTransaction gtx = cache.getCurrentTransaction();
cache.put("/a/b/c", null);
cache.put("/a/b/c", null);
DataNode n=cache.get("/a");
IdentityLock lock=n.getLock();
int num=lock.getReaderOwners().size();
assertEquals(0, num);
// make sure this is write locked.
assertLocked(gtx, "/a", true);
n=cache.get("/a/b");
lock=n.getLock();
num=lock.getReaderOwners().size();
assertEquals(0, num);
// make sure this is write locked.
assertLocked(gtx, "/a/b", true);
n=cache.get("/a/b/c");
lock=n.getLock();
num=lock.getReaderOwners().size();
assertEquals(0, num);
// make sure this is write locked.
assertLocked(gtx, "/a/b/c", true);
tx.rollback();
assertEquals(0, cache.getNumberOfLocksHeld());
}
private void assertLocked(Object owner, String fqn, boolean write_locked) throws Exception{
DataNode n=cache.peek(Fqn.fromString(fqn));
IdentityLock lock=n.getLock();
if(owner == null)
owner=Thread.currentThread();
assertTrue("node " + fqn + " is not locked", lock.isLocked());
if(write_locked) {
assertTrue("node " + fqn + " is not write-locked" + (lock.isReadLocked() ? " but is read-locked instead!" : "!"), lock.isWriteLocked());
}
else {
assertTrue("node " + fqn + " is not read-locked" + (lock.isWriteLocked() ? " but is write-locked instead!" : "!"), lock.isReadLocked());
}
assertTrue("owner " + owner + "is not owner", lock.isOwner(owner));
}
public void testConcurrentNodeAccessOnRemovalWithTx() throws Exception
{
cache.put("/a/b/c", null);
tx.begin();
cache.remove("/a/b/c");
// this node should now be locked.
Transaction t = cache.getTransactionManager().suspend();
Transaction t2 = null;
try
{
System.out.println(cache.printLockInfo());
// start a new tx
cache.getTransactionManager().begin();
t2 = cache.getTransactionManager().getTransaction();
cache.get("/a/b/c"); // should fail
t2.commit();
fail("Should not be able to get a hold of /a/b/c until the deleting tx completes");
}
catch (Exception e)
{
// expected
t2.commit();
}
cache.getTransactionManager().resume(t);
tx.rollback();
assertNotNull(cache.get("/a/b/c"));
assertEquals(0, cache.getNumberOfLocksHeld());
}
public void testConcurrentNodeAccessOnRemovalWithoutTx() throws Exception
{
cache.put("/a/b/c", null);
tx.begin();
cache.remove("/a/b/c");
// this node should now be locked.
Transaction t = cache.getTransactionManager().suspend();
Thread th = new Thread(){
public void run(){
try
{
System.out.println(cache.printLockInfo());
cache.get("/a/b/c"); // should fail
fail("Should not be able to get a hold of /a/b/c until the deleting tx completes");
}
catch (Exception e)
{
// expected
}
}
};
th.start();
th.join();
cache.getTransactionManager().resume(t);
tx.rollback();
assertNotNull(cache.get("/a/b/c"));
assertEquals(0, cache.getNumberOfLocksHeld());
}
public void testRemove() throws CacheException, SystemException, NotSupportedException, HeuristicMixedException, HeuristicRollbackException, RollbackException {
cache.put("/a/b/c", null);
cache.put("/a/b/c/1", null);
cache.put("/a/b/c/2", null);
cache.put("/a/b/c/3", null);
cache.put("/a/b/c/3/a/b/c", null);
assertEquals(0, cache.getNumberOfLocksHeld());
assertEquals(0, cache.getLockTable().size());
tx.begin();
cache.remove("/a/b/c");
System.out.println("locks held (after removing /a/b/c): \n" + cache.printLockInfo());
// this used to test for 2 locks held. After the fixes for JBCACHE-875 however, 2 more locks are acquired - for the root node as well as the deleted node.
// and since we would lock all children of the deleted node as well, we have 10 locks here.
assertEquals(10, cache.getNumberOfLocksHeld());
tx.commit();
System.out.println("locks held (after committing /a/b/c): \n" + cache.printLockInfo());
assertEquals(0, cache.getNumberOfLocksHeld());
}
public void testRemoveAndRollback() throws CacheException, SystemException, NotSupportedException, HeuristicMixedException, HeuristicRollbackException, RollbackException {
cache.put("/a/b/c", null);
cache.put("/a/b/c/1", null);
cache.put("/a/b/c/2", null);
cache.put("/a/b/c/3", null);
cache.put("/a/b/c/3/a/b/c", null);
assertEquals(0, cache.getNumberOfLocksHeld());
assertEquals(0, cache.getLockTable().size());
tx.begin();
System.out.println("locks held (before removing /a/b/c): \n" + cache.printLockInfo());
cache.remove("/a/b/c");
System.out.println("locks held (after removing /a/b/c): \n" + cache.printLockInfo());
assertEquals(10, cache.getNumberOfLocksHeld());
tx.rollback();
System.out.println("locks held (after rollback): \n" + cache.printLockInfo());
assertEquals(0, cache.getNumberOfLocksHeld());
assertTrue(cache.exists("/a/b/c"));
assertTrue(cache.exists("/a/b/c/1"));
assertTrue(cache.exists("/a/b/c/2"));
assertTrue(cache.exists("/a/b/c/3"));
assertTrue(cache.exists("/a/b/c/3/a"));
assertTrue(cache.exists("/a/b/c/3/a/b"));
assertTrue(cache.exists("/a/b/c/3/a/b/c"));
}
public void testRemoveKeyRollback() throws CacheException, SystemException, NotSupportedException {
cache.put("/bela/ban", "name", "Bela");
tx.begin();
cache.remove("/bela/ban", "name");
assertNull(cache.get("/bela/ban", "name"));
tx.rollback();
assertEquals("Bela", cache.get("/bela/ban", "name"));
}
public void testRemoveKeyRollback2() {
try {
Map m=new HashMap();
m.put("name", "Bela");
m.put("id", new Integer(322649));
cache.put("/bela/ban", m);
tx.begin();
cache.remove("/bela/ban", "name");
assertNull(cache.get("/bela/ban", "name"));
tx.rollback();
assertEquals("Bela", cache.get("/bela/ban", "name"));
}
catch(Throwable t) {
t.printStackTrace();
fail(t.toString());
}
}
public void testRemoveKeyRollback3() {
try {
cache.put("/bela/ban", "name", "Bela");
tx.begin();
cache.put("/bela/ban", "name", "Michelle");
cache.remove("/bela/ban", "name");
assertNull(cache.get("/bela/ban", "name"));
tx.rollback();
assertEquals("Bela", cache.get("/bela/ban", "name"));
}
catch(Throwable t) {
t.printStackTrace();
fail(t.toString());
}
}
public void testDoubleRemovalOfSameData() {
try {
tx.begin();
cache.put("/foo/1", "item", new Integer(1));
assertEquals(cache.get("/foo/1", "item"), new Integer(1));
cache.remove("/foo/1");
assertNull(cache.get("/foo/1", "item"));
cache.remove("/foo/1");
assertNull(cache.get("/foo/1", "item"));
tx.rollback();
assertFalse(cache.exists("/foo/1"));
assertNull(cache.get("/foo/1", "item"));
}
catch(Throwable t) {
t.printStackTrace();
fail(t.toString());
}
}
/**
* put(Fqn, Map) with a previous null map
*/
public void testPutDataRollback1() {
try {
cache.put("/bela/ban", null); // create a node /bela/ban with a null map
tx.begin();
Map m=new HashMap();
m.put("name", "Bela");
m.put("id", new Integer(322649));
cache.put("/bela/ban", m);
tx.rollback();
DataNode n=cache.get("/bela/ban");
if(n.getData() == null) return;
assertEquals("map should be empty", 0, n.getData().size());
}
catch(Throwable t) {
t.printStackTrace();
fail(t.toString());
}
}
/**
* put(Fqn, Map) with a previous non-null map
*/
public void testputDataRollback2() {
Map m1, m2;
m1=new HashMap();
m1.put("name", "Bela");
m1.put("id", new Integer(322649));
m2=new HashMap();
m2.put("other", "bla");
m2.put("name", "Michelle");
try {
cache.put("/bela/ban", m1);
tx.begin();
cache.put("/bela/ban", m2);
Map tmp=cache.get("/bela/ban").getData();
assertEquals(3, tmp.size());
assertEquals("Michelle", tmp.get("name"));
assertEquals(tmp.get("id"), new Integer(322649));
assertEquals("bla", tmp.get("other"));
tx.rollback();
tmp=cache.get("/bela/ban").getData();
assertEquals(2, tmp.size());
assertEquals("Bela", tmp.get("name"));
assertEquals(tmp.get("id"), new Integer(322649));
}
catch(Throwable t) {
t.printStackTrace();
fail(t.toString());
}
}
public void testPutRollback() {
try {
cache.put("/bela/ban", null); // /bela/ban needs to exist
tx.begin();
cache.put("/bela/ban", "name", "Bela");
assertEquals("Bela", cache.get("/bela/ban", "name"));
tx.rollback();
assertNull(cache.get("/bela/ban", "name"));
}
catch(Throwable t) {
t.printStackTrace();
fail(t.toString());
}
}
public void testPutRollback2() {
try {
cache.put("/bela/ban", "name", "Bela"); // /bela/ban needs to exist
tx.begin();
cache.put("/bela/ban", "name", "Michelle");
assertEquals("Michelle", cache.get("/bela/ban", "name"));
tx.rollback();
assertEquals("Bela", cache.get("/bela/ban", "name"));
}
catch(Throwable t) {
t.printStackTrace();
fail(t.toString());
}
}
public void testSimpleRollbackTransactions() throws Exception {
final Fqn FQN=Fqn.fromString("/a/b/c");
tx.begin();
cache.put(FQN, "entry", "commit");
tx.commit();
tx.begin();
cache.put(FQN, "entry", "rollback");
cache.remove(FQN);
tx.rollback();
assertEquals("Node should keep the commited value", "commit", cache.get(FQN).get("entry"));
tx.begin();
cache.remove(FQN);
cache.put(FQN, "entry", "rollback");
tx.rollback();
assertEquals("Node should keep the commited value", "commit", cache.get(FQN).get("entry"));
}
private Transaction startTransaction() throws Exception {
DummyTransactionManager mgr=DummyTransactionManager.getInstance();
mgr.begin();
return mgr.getTransaction();
}
public void testConcurrentReadAndWriteAccess() throws Exception {
cache.setIsolationLevel(IsolationLevel.REPEATABLE_READ);
cache.put("/1/2/3/4", "foo", "bar"); // no TX, no locks held after put() returns
class Reader extends Thread {
Transaction thread_tx;
public Reader() {
super("Reader");
}
public void run() {
try {
thread_tx=startTransaction();
log("acquiring RL");
cache.get("/1/2/3", "foo"); // acquires RLs on all 3 nodes
log("RL acquired successfully");
sleep(2000);
log("committing TX");
thread_tx.commit(); // releases RLs
log("committed TX");
}
catch(Exception e) {
thread_ex=e;
}
}
}
class Writer extends Thread {
Transaction thread_tx;
public Writer() {
super("Writer");
}
public void run() {
try {
sleep(500); // give the Reader a chance to acquire the RLs
thread_tx=startTransaction();
log("acquiring WL");
cache.put("/1", "foo", "bar2"); // needs to acquired a WL on /1
log("acquired WL successfully");
log("committing TX");
thread_tx.commit();
log("committed TX");
}
catch(Exception e) {
thread_ex=e;
}
}
}
Reader reader=new Reader();
Writer writer=new Writer();
reader.start();
writer.start();
reader.join();
writer.join();
if(thread_ex != null)
throw thread_ex;
}
public void testRemoveAndGetInTx() throws Exception
{
Fqn A_B = Fqn.fromString("/a/b");
Fqn A = Fqn.fromString("/a");
cache.put(A_B, "k", "v");
assertTrue(cache.exists(A_B));
assertTrue(cache.exists(A));
cache.getTransactionManager().begin();
cache.remove(A);
cache.get(A_B, "k");
cache.getTransactionManager().commit();
}
public void testRemoveAndPutInTx() throws Exception
{
Fqn A_B = Fqn.fromString("/a/b");
Fqn A = Fqn.fromString("/a");
cache.put(A_B, "k", "v");
assertTrue(cache.exists(A_B));
assertTrue(cache.exists(A));
cache.getTransactionManager().begin();
cache.remove(A_B);
cache.put(A_B, "k", "v2");
cache.getTransactionManager().commit();
assertTrue(cache.exists(A_B));
assertTrue(cache.exists(A));
assertEquals("v2", cache.get(A_B, "k"));
}
public void testRemoveParentAndPutInTx() throws Exception
{
Fqn A_B = Fqn.fromString("/a/b");
Fqn A = Fqn.fromString("/a");
cache.put(A_B, "k", "v");
assertTrue(cache.exists(A_B));
assertTrue(cache.exists(A));
cache.getTransactionManager().begin();
cache.remove(A);
cache.put(A_B, "k", "v2");
cache.getTransactionManager().commit();
assertTrue(cache.exists(A_B));
assertTrue(cache.exists(A));
assertEquals("v2", cache.get(A_B, "k"));
}
public void testRemoveGrandParentAndPutInTx() throws Exception
{
Fqn A_B_C = Fqn.fromString("/a/b/c");
Fqn A = Fqn.fromString("/a");
cache.put(A_B_C, "k", "v");
assertTrue(cache.exists(A_B_C));
assertTrue(cache.exists(A));
cache.getTransactionManager().begin();
cache.remove(A);
cache.put(A_B_C, "k", "v2");
cache.getTransactionManager().commit();
assertTrue(cache.exists(A_B_C));
assertTrue(cache.exists(A));
assertEquals("v2", cache.get(A_B_C, "k"));
}
// thanks to msteiner at gazeta.pl for this test and spotting JBCACHE-999
public void testRootNodeRemoval() throws Exception
{
Fqn root = Fqn.ROOT;
Fqn fqn = new Fqn(root, new Long(1));
//put first time
tx.begin();
this.cache.put(fqn, "k", "v");
tx.commit();
//get works fine
tx.begin();
assertEquals("v", this.cache.get(fqn, "k"));
tx.commit();
//remove all
tx.begin();
this.cache.remove(root);
tx.commit();
//get returns null - ok
//put - endless loop
tx.begin();
assertNull(this.cache.get(fqn, "k"));
this.cache.put(fqn, "k", "v");
tx.commit();
}
// thanks to msteiner at gazeta.pl for this test and spotting JBCACHE-999
public void testNodeAdditionAfterRemoval() throws Exception
{
Fqn fqn = Fqn.fromString("/1/2/3/4");
//put first time
tx.begin();
this.cache.put(fqn, "k", "v");
tx.commit();
//get works fine
tx.begin();
assertEquals("v", this.cache.get(fqn, "k"));
tx.commit();
//remove all
tx.begin();
this.cache.remove(Fqn.ROOT);
tx.commit();
//get returns null - ok
//put - endless loop
tx.begin();
assertNull(this.cache.get(fqn, "k"));
this.cache.put(fqn, "k", "v");
tx.commit();
}
// thanks to msteiner at gazeta.pl for this test and spotting JBCACHE-999
public void testRootNodeRemovalRollback() throws Exception
{
Fqn root = Fqn.ROOT;
Fqn fqn = new Fqn(root, new Long(1));
//put first time
tx.begin();
this.cache.put(fqn, "k", "v");
tx.commit();
//get works fine
tx.begin();
assertEquals("v", this.cache.get(fqn, "k"));
tx.commit();
//remove all
tx.begin();
this.cache.remove(root);
tx.rollback();
assertEquals("v", this.cache.get(fqn, "k"));
}
/* public void testConcurrentReadAccess() throws Exception {
cache.setIsolationLevel(IsolationLevel.REPEATABLE_READ);
cache.setUseInterceptorMbeans(false);
cache.put("/1/2/3/4", "foo", "bar"); // no TX, no locks held after put() returns
class Reader extends Thread {
Transaction thread_tx;
public Reader(String name) {
super(name);
}
public void run() {
try {
thread_tx=startTransaction();
log("acquiring RL");
cache.get("/1/2/3", "foo"); // acquires RLs on all 3 nodes
log("RL acquired successfully");
sleep(200000);
log("committing TX");
thread_tx.commit(); // releases RLs
log("committed TX");
}
catch(Exception e) {
thread_ex=e;
}
}
}
Reader reader=new Reader("R1");
Reader reader2=new Reader("R2");
reader.start();
reader2.start();
reader.join();
reader2.join();
if(thread_ex != null)
throw thread_ex;
}
*/
private static void log(String msg) {
System.out.println(Thread.currentThread().getName() + ": " + msg);
}
// public void testRaceConditionOnNotInCacheCondition() throws Exception {
// cache.setIsolationLevel(IsolationLevel.SERIALIZABLE);
//
// tx.begin();
// // we now read the null entry, and decide that we need to go do something.
//
// Object cachedObject=cache.get("/SecurityInfo/", Integer.toString(23));
// assertNull(cachedObject); // we expect this in this test
//
// /**
// * now start another Thread to go do the same action, looking for the value, but it SHOULD
// * see the result of the main thread put once it commits.
// */
// Thread thread=new Thread(new Runnable() {
// UserTransaction tx2=(UserTransaction)new InitialContext(p).lookup("UserTransaction");
// public void run() {
// try {
// tx2.begin();
// log("OtherThread: inspecting the cache");
// Object cachedObject=cache.get("/SecurityInfo", Integer.toString(23));
//
// log("OtherThread: read from cache: " + cachedObject);
// Thread.sleep(3000);
//
// cachedObject=cache.get("/SecurityInfo", Integer.toString(23));
// log("OtherThread: read(second time) from cache:" + cachedObject);
//
// /**
// * This should really fail because the other thread should actually have put something else there.
// */
// cache.put("/SecurityInfo", Integer.toString(23), "HelloWorldDIRTY!");
//
// log("OtherThread: Has put something in the cache tha shouldn't be there");
// }
// catch(Exception e) {
// e.printStackTrace();
// }
// finally {
// if(tx2 != null)
// try {tx2.commit();} catch(Exception e) {e.printStackTrace();}
// }
// log("OthreThread: exiting");
//
// }
// });
//
// thread.start();
// log("MainThread is now waiting a little bit");
// Thread.sleep(2000); // wait long enough for the other thread to block a bit. Simulate a DB read
// log("MainThread is now putting something in the cache");
// cache.put("/SecurityInfo", Integer.toString(23), "HelloWorld");
// Thread.sleep(2000); // wait long enough for the other thread to block a bit. Simulate a DB read
// tx.commit();
// log("MainThread: committed");
// thread.join(30000);
//
// log(cache.get("/SecurityInfo", Integer.toString(23)).toString());
// }
// public void testConcurrentReadsWithSerializableIsolationLevel() throws CacheException, InterruptedException {
// final long TIMEOUT=5000;
// cache.setIsolationLevel(IsolationLevel.SERIALIZABLE);
// cache.put("/testfqn", "testkey", "testvalue"); // add initial value, to be read by threads
// Reader r1, r2;
// r1=new Reader("reader1", 3000);
// r2=new Reader("reader2", 0);
// r1.start();
// pause(100); // make sure thread1 starts and acquires the lock before thread2
// r2.start();
// r1.join(TIMEOUT);
// r2.join(TIMEOUT);
// }
//
// class Reader extends Thread {
// long timeout;
//
// public Reader(String name, long timeout) {
// super(name);
// this.timeout=timeout;
// }
//
// public void run() {
// UserTransaction trans=null;
// try {
// trans=(UserTransaction)new InitialContext(p).lookup("UserTransaction");
// trans.begin();
// Object retval=null;
// log2("accessing tree");
// retval=cache.get("testfqn", "testkey");
// log2("retval: " + retval);
// if(timeout > 0) {
// log2("sleeping for " + timeout + " ms");
// pause(timeout);
// }
// }
// catch(Exception e) {
// e.printStackTrace();
// }
// finally {
// try {trans.commit();} catch(Throwable t) {}
// log2("done");
// }
// }
// }
// private void pause(long timeout) {
// try {
// Thread.sleep(timeout);
// }
// catch(InterruptedException e) {
// }
// }
//
// private void log(String msg) {
// System.out.println("-- [" + Thread.currentThread() + "]: " + msg);
// }
//
// private void log2(String msg) {
// System.out.println("-- [" + System.currentTimeMillis() + " " + Thread.currentThread() + "]: " + msg);
// }
public static Test suite() throws Exception {
return new TestSuite(TransactionTest.class);
}
public static void main(String[] args) throws Exception {
junit.textui.TestRunner.run(suite());
}
}