package org.jboss.cache.api.mvcc;
import org.jboss.cache.Cache;
import org.jboss.cache.Fqn;
import org.jboss.cache.UnitTestCacheFactory;
import org.jboss.cache.config.Configuration.CacheMode;
import org.jboss.cache.config.Configuration.NodeLockingScheme;
import org.jboss.cache.factories.UnitTestCacheConfigurationFactory;
import org.jboss.cache.invocation.InvocationContextContainer;
import org.jboss.cache.lock.IsolationLevel;
import org.jboss.cache.lock.LockManager;
import org.jboss.cache.lock.TimeoutException;
import org.jboss.cache.transaction.DummyTransactionManagerLookup;
import org.jboss.cache.util.TestingUtil;
import org.testng.annotations.AfterMethod;
import org.testng.annotations.BeforeMethod;
import org.testng.annotations.Test;
import javax.transaction.Transaction;
import javax.transaction.TransactionManager;
import java.util.Collections;
/**
* @author Manik Surtani (<a href="mailto:manik AT jboss DOT org">manik AT jboss DOT org</a>)
* @since 3.0
*/
@Test(groups = {"functional", "mvcc"})
public abstract class LockTestBase
{
protected Fqn A = Fqn.fromString("/a");
protected Fqn AB = Fqn.fromString("/a/b");
protected Fqn ABC = Fqn.fromString("/a/b/c");
protected Fqn ABCD = Fqn.fromString("/a/b/c/d");
protected boolean repeatableRead = true;
protected boolean lockParentForChildInsertRemove = false;
protected class LockTestBaseTL {
public Cache<String, String> cache;
public TransactionManager tm;
public LockManager lockManager;
public InvocationContextContainer icc;
}
protected ThreadLocal<LockTestBaseTL> threadLocal = new ThreadLocal<LockTestBaseTL>();
@BeforeMethod
public void setUp()
{
LockTestBaseTL tl = new LockTestBaseTL();
tl.cache = new UnitTestCacheFactory<String, String>().createCache(UnitTestCacheConfigurationFactory.createConfiguration(CacheMode.LOCAL), false);
tl.cache.getConfiguration().setNodeLockingScheme(NodeLockingScheme.MVCC);
tl.cache.getConfiguration().setTransactionManagerLookupClass(DummyTransactionManagerLookup.class.getName());
tl.cache.getConfiguration().setIsolationLevel(repeatableRead ? IsolationLevel.REPEATABLE_READ : IsolationLevel.READ_COMMITTED);
tl.cache.getConfiguration().setLockParentForChildInsertRemove(lockParentForChildInsertRemove);
// reduce lock acquisition timeout so this doesn't take forever to run
tl.cache.getConfiguration().setLockAcquisitionTimeout(200); // 200 ms
tl.cache.start();
tl.lockManager = TestingUtil.extractComponentRegistry(tl.cache).getComponent(LockManager.class);
tl.icc = TestingUtil.extractComponentRegistry(tl.cache).getComponent(InvocationContextContainer.class);
tl.tm = TestingUtil.extractComponentRegistry(tl.cache).getComponent(TransactionManager.class);
threadLocal.set(tl);
}
@AfterMethod
public void tearDown()
{
LockTestBaseTL tl = threadLocal.get();
TestingUtil.killCaches(tl.cache);
threadLocal.set(null);
}
protected void assertLocked(Fqn fqn)
{
LockTestBaseTL tl = threadLocal.get();
LockAssert.assertLocked(fqn, tl.lockManager, tl.icc);
}
protected void assertNotLocked(Fqn fqn)
{
LockTestBaseTL tl = threadLocal.get();
LockAssert.assertNotLocked(fqn, tl.lockManager, tl.icc);
}
protected void assertNoLocks()
{
LockTestBaseTL tl = threadLocal.get();
LockAssert.assertNoLocks(tl.lockManager, tl.icc);
}
public void testLocksOnPutKeyVal() throws Exception
{
LockTestBaseTL tl = threadLocal.get();
tl.tm.begin();
tl.cache.put(AB, "k", "v");
if (lockParentForChildInsertRemove)
assertLocked(Fqn.ROOT);
else
assertNotLocked(Fqn.ROOT);
assertLocked(A);
assertLocked(AB);
assertNotLocked(ABC);
tl.tm.commit();
assertNoLocks();
tl.tm.begin();
assert tl.cache.get(AB, "k").equals("v");
assertNotLocked(Fqn.ROOT);
assertNotLocked(A);
assertNotLocked(AB);
assertNotLocked(ABC);
tl.tm.commit();
assertNoLocks();
tl.tm.begin();
tl.cache.put(ABC, "k", "v");
assertNotLocked(Fqn.ROOT);
assertNotLocked(A);
if (lockParentForChildInsertRemove)
assertLocked(AB);
else
assertNotLocked(AB);
assertLocked(ABC);
tl.tm.commit();
assertNoLocks();
}
public void testLocksOnPutData() throws Exception
{
LockTestBaseTL tl = threadLocal.get();
tl.tm.begin();
tl.cache.put(AB, Collections.singletonMap("k", "v"));
if (lockParentForChildInsertRemove)
assertLocked(Fqn.ROOT);
else
assertNotLocked(Fqn.ROOT);
assertLocked(A);
assertLocked(AB);
assertNotLocked(ABC);
assert "v".equals(tl.cache.get(AB, "k"));
tl.tm.commit();
assert "v".equals(tl.cache.get(AB, "k"));
assertNoLocks();
tl.tm.begin();
assert "v".equals(tl.cache.get(AB, "k"));
assertNotLocked(Fqn.ROOT);
assertNotLocked(A);
assertNotLocked(AB);
assertNotLocked(ABC);
tl.tm.commit();
assertNoLocks();
tl.tm.begin();
tl.cache.put(ABC, Collections.singletonMap("k", "v"));
assertNotLocked(Fqn.ROOT);
assertNotLocked(A);
if (lockParentForChildInsertRemove)
assertLocked(AB);
else
assertNotLocked(AB);
assertLocked(ABC);
tl.tm.commit();
assertNoLocks();
}
public void testLocksOnRemoveNode() throws Exception
{
LockTestBaseTL tl = threadLocal.get();
// init some data on a node
tl.cache.put(AB, Collections.singletonMap("k", "v"));
assert "v".equals(tl.cache.get(AB, "k"));
tl.tm.begin();
tl.cache.removeNode(AB);
assertLocked(AB);
if (lockParentForChildInsertRemove)
assertLocked(A);
else
assertNotLocked(A);
assertNotLocked(Fqn.ROOT);
tl.tm.commit();
assert tl.cache.getNode(AB) == null : "Should not exist";
assertNoLocks();
}
public void testLocksOnEvictNode() throws Exception
{
LockTestBaseTL tl = threadLocal.get();
// init some data on a node
tl.cache.put(AB, Collections.singletonMap("k", "v"));
assert "v".equals(tl.cache.get(AB, "k"));
tl.tm.begin();
tl.cache.evict(AB);
assertLocked(AB);
if (lockParentForChildInsertRemove)
assertLocked(A);
else
assertNotLocked(A);
assertNotLocked(Fqn.ROOT);
tl.tm.commit();
assert tl.cache.getNode(AB) == null : "Should not exist";
assertNoLocks();
}
public void testLocksOnEvictRecursiveNode() throws Exception
{
LockTestBaseTL tl = threadLocal.get();
// init some data on a node
tl.cache.put(AB, Collections.singletonMap("k", "v"));
tl.cache.put(ABC, Collections.singletonMap("k", "v"));
tl.cache.put(ABCD, Collections.singletonMap("k", "v"));
assert "v".equals(tl.cache.get(AB, "k"));
assert "v".equals(tl.cache.get(ABC, "k"));
assert "v".equals(tl.cache.get(ABCD, "k"));
tl.tm.begin();
tl.cache.evict(AB, true);
assertLocked(AB);
assertLocked(ABC);
assertLocked(ABCD);
if (lockParentForChildInsertRemove)
assertLocked(A);
else
assertNotLocked(A);
assertNotLocked(Fqn.ROOT);
tl.tm.commit();
assert tl.cache.getNode(AB) == null : "Should not exist";
assertNoLocks();
}
public void testLocksOnRemoveNonexistentNode() throws Exception
{
LockTestBaseTL tl = threadLocal.get();
assert tl.cache.getNode(AB) == null : "Should not exist";
tl.tm.begin();
tl.cache.removeNode(AB);
assertLocked(AB);
if (lockParentForChildInsertRemove)
assertLocked(A);
else
assertNotLocked(A);
assertNotLocked(Fqn.ROOT);
tl.tm.commit();
assert tl.cache.getNode(AB) == null : "Should not exist";
assertNoLocks();
}
public void testLocksOnEvictNonexistentNode() throws Exception
{
LockTestBaseTL tl = threadLocal.get();
assert tl.cache.getNode(AB) == null : "Should not exist";
tl.tm.begin();
tl.cache.evict(AB);
assertLocked(AB);
if (lockParentForChildInsertRemove)
assertLocked(A);
else
assertNotLocked(A);
assertNotLocked(Fqn.ROOT);
tl.tm.commit();
assert tl.cache.getNode(AB) == null : "Should not exist";
assertNoLocks();
}
public void testLocksOnRemoveData() throws Exception
{
LockTestBaseTL tl = threadLocal.get();
// init some data on a node
tl.cache.put(AB, "k", "v");
tl.cache.put(AB, "k2", "v2");
assert "v".equals(tl.cache.get(AB, "k"));
assert "v2".equals(tl.cache.get(AB, "k2"));
// remove
tl.tm.begin();
Object x = tl.cache.remove(AB, "k");
assert x.equals("v");
assertLocked(AB);
assertNotLocked(A);
assertNotLocked(Fqn.ROOT);
tl.tm.commit();
assert tl.cache.get(AB, "k") == null : "Should not exist";
assert "v2".equals(tl.cache.get(AB, "k2"));
assertNoLocks();
// clearData
tl.tm.begin();
tl.cache.clearData(AB);
assertLocked(AB);
assertNotLocked(A);
assertNotLocked(Fqn.ROOT);
tl.tm.commit();
assert tl.cache.get(AB, "k") == null : "Should not exist";
assert tl.cache.get(AB, "k2") == null : "Should not exist";
assertNoLocks();
// nonexistent key
assert tl.cache.get(AB, "k3") == null : "Should not exist";
tl.tm.begin();
tl.cache.remove(AB, "k3");
assertLocked(AB);
assertNotLocked(A);
assertNotLocked(Fqn.ROOT);
tl.tm.commit();
assertNoLocks();
}
public void testLocksOnRemoveDataNonExistentNode() throws Exception
{
LockTestBaseTL tl = threadLocal.get();
assert tl.cache.getNode(AB) == null : "Should not exist";
tl.tm.begin();
tl.cache.remove(AB, "k");
assertNotLocked(AB);
assertNotLocked(A);
assertNotLocked(Fqn.ROOT);
tl.tm.commit();
assert tl.cache.getNode(AB) == null : "Should not exist";
}
public void testReadMethods() throws Exception
{
LockTestBaseTL tl = threadLocal.get();
tl.cache.put(AB, "k", "v");
tl.tm.begin();
assert "v".equals(tl.cache.get(AB, "k"));
assertNoLocks();
tl.tm.commit();
assertNoLocks();
tl.tm.begin();
assert tl.cache.getData(AB).containsKey("k");
assertNoLocks();
tl.tm.commit();
assertNoLocks();
tl.tm.begin();
assert tl.cache.getKeys(AB).contains("k");
assertNoLocks();
tl.tm.commit();
assertNoLocks();
tl.tm.begin();
assert tl.cache.getNode(AB) != null;
assertNoLocks();
tl.tm.commit();
assertNoLocks();
tl.tm.begin();
assert tl.cache.getNode(A) != null;
assert !(tl.cache.getNode(A).getChildrenNames().isEmpty());
assert tl.cache.getNode(A).getChildrenNames().contains(AB.getLastElement());
assertNoLocks();
tl.tm.commit();
assertNoLocks();
}
public void testWriteDoesntBlockRead() throws Exception
{
LockTestBaseTL tl = threadLocal.get();
tl.cache.put(AB, "k", "v");
// start a write.
tl.tm.begin();
tl.cache.put(AB, "k2", "v2");
assertLocked(AB);
Transaction write = tl.tm.suspend();
// now start a read and confirm that the write doesn't block it.
tl.tm.begin();
assert "v".equals(tl.cache.get(AB, "k"));
assert null == tl.cache.get(AB, "k2") : "Should not see uncommitted changes";
Transaction read = tl.tm.suspend();
// commit the write
tl.tm.resume(write);
tl.tm.commit();
assertNoLocks();
tl.tm.resume(read);
if (repeatableRead)
assert null == tl.cache.get(AB, "k2") : "Should have repeatable read";
else
assert "v2".equals(tl.cache.get(AB, "k2")) : "Read committed should see committed changes";
tl.tm.commit();
assertNoLocks();
}
public void testWriteDoesntBlockReadNonexistent() throws Exception
{
LockTestBaseTL tl = threadLocal.get();
// start a write.
tl.tm.begin();
tl.cache.put(AB, "k", "v");
assertLocked(AB);
Transaction write = tl.tm.suspend();
// now start a read and confirm that the write doesn't block it.
tl.tm.begin();
assert null == tl.cache.get(AB, "k") : "Should not see uncommitted changes";
assert null == tl.cache.getNode(AB);
Transaction read = tl.tm.suspend();
// commit the write
tl.tm.resume(write);
tl.tm.commit();
assertNoLocks();
tl.tm.resume(read);
if (repeatableRead)
{
assert null == tl.cache.get(AB, "k") : "Should have repeatable read";
assert null == tl.cache.getNode(AB);
}
else
{
assert "v".equals(tl.cache.get(AB, "k")) : "Read committed should see committed changes";
assert null != tl.cache.getNode(AB);
}
tl.tm.commit();
assertNoLocks();
}
public void testConcurrentWriters() throws Exception
{
LockTestBaseTL tl = threadLocal.get();
tl.tm.begin();
tl.cache.put(AB, "k", "v");
Transaction t1 = tl.tm.suspend();
tl.tm.begin();
try
{
tl.cache.put(AB, "k", "v");
assert false : "Should fail lock acquisition";
}
catch (TimeoutException expected)
{
// expected.printStackTrace(); // for debugging
}
tl.tm.commit();
tl.tm.resume(t1);
tl.tm.commit();
assertNoLocks();
}
public void testRollbacks() throws Exception
{
LockTestBaseTL tl = threadLocal.get();
tl.cache.put(AB, "k", "v");
tl.tm.begin();
assert "v".equals(tl.cache.get(AB, "k"));
Transaction reader = tl.tm.suspend();
tl.tm.begin();
tl.cache.put(AB, "k", "v2");
tl.tm.rollback();
tl.tm.resume(reader);
assert "v".equals(tl.cache.get(AB, "k")) : "Expecting 'v' but was " + tl.cache.get(AB, "k");
tl.tm.commit();
// even after commit
assert "v".equals(tl.cache.get(AB, "k"));
assertNoLocks();
}
public void testRollbacksOnNullNode() throws Exception
{
LockTestBaseTL tl = threadLocal.get();
tl.tm.begin();
assert null == tl.cache.get(AB, "k");
assert null == tl.cache.getNode(AB);
Transaction reader = tl.tm.suspend();
tl.tm.begin();
tl.cache.put(AB, "k", "v");
assert null != tl.cache.getNode(AB);
assert "v".equals(tl.cache.get(AB, "k"));
tl.tm.rollback();
tl.tm.resume(reader);
assert null == tl.cache.get(AB, "k") : "Expecting null but was " + tl.cache.get(AB, "k");
assert null == tl.cache.getNode(AB);
tl.tm.commit();
// even after commit
assert null == tl.cache.get(AB, "k");
assert null == tl.cache.getNode(AB);
assertNoLocks();
}
public void testPhantomChildren() throws Exception
{
LockTestBaseTL tl = threadLocal.get();
tl.cache.put(AB, "k", "v");
assert tl.cache.getNode(AB).getChildren().size() == 0;
assert tl.cache.getNode(A).getChildren().size() == 1;
tl.tm.begin();
tl.cache.put(ABC, "k", "v");
assert tl.cache.getRoot().hasChild(ABC);
assert tl.cache.getNode(ABC) != null;
assert tl.cache.getNode(AB).getChild(ABC.getLastElement()) != null;
assert tl.cache.getNode(AB).getChildren().size() == 1;
Transaction t = tl.tm.suspend();
assert tl.cache.getNode(ABC) == null;
assert tl.cache.getNode(AB).getChild(ABC.getLastElement()) == null;
assert tl.cache.getNode(AB).getChildren().size() == 0;
tl.tm.resume(t);
assert tl.cache.getRoot().hasChild(ABC);
assert tl.cache.getNode(ABC) != null;
tl.tm.commit();
assert tl.cache.getNode(ABC) != null;
assert tl.cache.getNode(AB).getChild(ABC.getLastElement()) != null;
assert tl.cache.getNode(AB).getChildren().size() == 1;
}
public void testChildCount() throws Exception
{
LockTestBaseTL tl = threadLocal.get();
tl.cache.put(AB, "k", "v");
assert tl.cache.getNode(AB).getChildren().size() == 0;
assert tl.cache.getNode(A).getChildren().size() == 1;
tl.tm.begin();
assert tl.cache.getNode(AB).getChildren().size() == 0;
assert tl.cache.getNode(A).getChildren().size() == 1;
tl.cache.removeNode(AB);
assert tl.cache.getNode(A).getChildren().size() == 0;
assert tl.cache.getNode(A).hasChild(AB.getLastElement()) == false;
assert tl.cache.getNode(AB) == null;
Transaction t = tl.tm.suspend();
assert tl.cache.getNode(AB) != null;
assert tl.cache.getNode(A).getChild(AB.getLastElement()) != null;
assert tl.cache.getNode(A).getChildren().size() == 1;
tl.tm.resume(t);
assert tl.cache.getNode(A).getChildren().size() == 0;
assert tl.cache.getNode(A).hasChild(AB.getLastElement()) == false;
assert tl.cache.getNode(AB) == null;
tl.tm.commit();
assert tl.cache.getNode(A).getChildren().size() == 0;
assert tl.cache.getNode(A).hasChild(AB.getLastElement()) == false;
assert tl.cache.getNode(AB) == null;
}
public void testOverwritingOnInsert() throws Exception
{
LockTestBaseTL tl = threadLocal.get();
tl.cache.put(AB, "k", "v");
tl.tm.begin();
tl.cache.put(ABC, "k", "v");
assert "v".equals(tl.cache.get(ABC, "k"));
assert "v".equals(tl.cache.get(AB, "k"));
Transaction t1 = tl.tm.suspend();
tl.tm.begin();
tl.cache.put(AB, "k", "v2");
assert "v2".equals(tl.cache.get(AB, "k"));
assert null == tl.cache.get(ABC, "k");
Transaction t2 = tl.tm.suspend();
tl.tm.resume(t1);
t1.commit();
assert "v".equals(tl.cache.get(AB, "k"));
assert "v".equals(tl.cache.get(ABC, "k"));
tl.tm.resume(t2);
t2.commit();
assert "v2".equals(tl.cache.get(AB, "k"));
assert "v".equals(tl.cache.get(ABC, "k"));
}
public void testOverwritingOnInsert2() throws Exception
{
LockTestBaseTL tl = threadLocal.get();
tl.cache.put(AB, "k", "v");
tl.tm.begin();
tl.cache.put(AB, "k", "v2");
assert "v2".equals(tl.cache.get(AB, "k"));
assert null == tl.cache.get(ABC, "k");
Transaction t1 = tl.tm.suspend();
tl.tm.begin();
tl.cache.put(ABC, "k", "v");
assert "v".equals(tl.cache.get(ABC, "k"));
assert "v".equals(tl.cache.get(AB, "k"));
Transaction t2 = tl.tm.suspend();
tl.tm.resume(t1);
t1.commit();
assert "v2".equals(tl.cache.get(AB, "k"));
assert null == tl.cache.get(ABC, "k");
tl.tm.resume(t2);
t2.commit();
assert "v2".equals(tl.cache.get(AB, "k"));
assert "v".equals(tl.cache.get(ABC, "k"));
}
public void testOverwritingOnInsert3() throws Exception
{
LockTestBaseTL tl = threadLocal.get();
tl.cache.put(AB, "k", "v");
tl.tm.begin();
tl.cache.put(AB, "k", "v2");
assert "v2".equals(tl.cache.get(AB, "k"));
assert null == tl.cache.get(ABC, "k");
Transaction t1 = tl.tm.suspend();
tl.tm.begin();
tl.cache.put(ABC, "k", "v");
assert "v".equals(tl.cache.get(ABC, "k"));
assert "v".equals(tl.cache.get(AB, "k"));
tl.tm.commit();
assert "v".equals(tl.cache.get(ABC, "k"));
assert "v".equals(tl.cache.get(AB, "k"));
tl.tm.resume(t1);
t1.commit();
assert "v2".equals(tl.cache.get(AB, "k"));
assert "v".equals(tl.cache.get(ABC, "k"));
}
public void testConcurrentInsertRemove1() throws Exception
{
LockTestBaseTL tl = threadLocal.get();
tl.cache.put(AB, "k", "v");
tl.tm.begin();
tl.cache.put(ABC, "k", "v");
assert "v".equals(tl.cache.get(AB, "k"));
assert "v".equals(tl.cache.get(ABC, "k"));
Transaction t1 = tl.tm.suspend();
tl.tm.begin();
tl.cache.removeNode(AB);
assert null == tl.cache.get(ABC, "k");
assert null == tl.cache.get(AB, "k");
tl.tm.commit();
assert null == tl.cache.get(ABC, "k");
assert null == tl.cache.get(AB, "k");
tl.tm.resume(t1);
t1.commit();
assert null == tl.cache.get(ABC, "k");
assert null == tl.cache.get(AB, "k");
}
public void testConcurrentInsertRemove2() throws Exception
{
LockTestBaseTL tl = threadLocal.get();
tl.cache.put(AB, "k", "v");
tl.tm.begin();
tl.cache.removeNode(AB);
assert null == tl.cache.get(ABC, "k");
assert null == tl.cache.get(AB, "k");
Transaction t1 = tl.tm.suspend();
tl.tm.begin();
assert "v".equals(tl.cache.get(AB, "k"));
tl.cache.put(ABC, "k", "v");
assert "v".equals(tl.cache.get(ABC, "k"));
tl.tm.commit();
assert "v".equals(tl.cache.get(AB, "k"));
assert "v".equals(tl.cache.get(ABC, "k"));
tl.tm.resume(t1);
t1.commit();
assert null == tl.cache.get(ABC, "k");
assert null == tl.cache.get(AB, "k");
}
}