package org.infinispan.api.mvcc; import static org.testng.AssertJUnit.assertEquals; import static org.testng.AssertJUnit.assertFalse; import static org.testng.AssertJUnit.assertTrue; import java.util.Collections; import javax.transaction.Transaction; import javax.transaction.TransactionManager; import org.infinispan.Cache; import org.infinispan.configuration.cache.ConfigurationBuilder; import org.infinispan.manager.CacheContainer; import org.infinispan.test.AbstractInfinispanTest; import org.infinispan.test.TestingUtil; import org.infinispan.test.fwk.TestCacheManagerFactory; import org.infinispan.transaction.lookup.EmbeddedTransactionManagerLookup; import org.infinispan.transaction.tm.EmbeddedTransaction; import org.infinispan.transaction.tm.EmbeddedTransactionManager; import org.infinispan.util.concurrent.IsolationLevel; import org.infinispan.util.concurrent.locks.LockManager; import org.infinispan.util.logging.Log; import org.infinispan.util.logging.LogFactory; import org.testng.annotations.AfterMethod; import org.testng.annotations.BeforeMethod; import org.testng.annotations.Test; /** * @author Manik Surtani (<a href="mailto:manik@jboss.org">manik@jboss.org</a>) */ @Test(groups = "functional") public abstract class LockTestBase extends AbstractInfinispanTest { private Log log = LogFactory.getLog(LockTestBase.class); protected boolean repeatableRead = true; private CacheContainer cm; protected static final class LockTestData { public Cache<String, String> cache; public EmbeddedTransactionManager tm; public LockManager lockManager; } protected LockTestData lockTestData; @BeforeMethod public void setUp() { LockTestData ltd = new LockTestData(); ConfigurationBuilder defaultCfg = TestCacheManagerFactory.getDefaultCacheConfiguration(true); defaultCfg .locking() .isolationLevel(repeatableRead ? IsolationLevel.REPEATABLE_READ : IsolationLevel.READ_COMMITTED) .lockAcquisitionTimeout(TestingUtil.shortTimeoutMillis()) .transaction() .transactionManagerLookup(new EmbeddedTransactionManagerLookup()); cm = TestCacheManagerFactory.createCacheManager(defaultCfg); ltd.cache = cm.getCache(); ltd.lockManager = TestingUtil.extractComponentRegistry(ltd.cache).getComponent(LockManager.class); ltd.tm = (EmbeddedTransactionManager) TestingUtil.extractComponentRegistry(ltd.cache).getComponent(TransactionManager.class); lockTestData = ltd; } @AfterMethod public void tearDown() { log.debug("**** - STARTING TEARDOWN - ****"); TestingUtil.killCacheManagers(cm); lockTestData = null; } protected void assertLocked(Object key) { LockAssert.assertLocked(key, lockTestData.lockManager); } protected void assertNotLocked(Object key) { LockAssert.assertNotLocked(key, lockTestData.lockManager); } protected void assertNoLocks() { LockAssert.assertNoLocks(lockTestData.lockManager); } public void testLocksOnPutKeyVal() throws Exception { Cache<String, String> cache = lockTestData.cache; EmbeddedTransactionManager tm = lockTestData.tm; tm.begin(); cache.put("k", "v"); assertTrue(tm.getTransaction().runPrepare()); assertLocked("k"); tm.getTransaction().runCommit(false); assertNoLocks(); tm.begin(); assertEquals("v", cache.get("k")); assertNotLocked("k"); tm.commit(); assertNoLocks(); tm.begin(); cache.remove("k"); assertTrue(tm.getTransaction().runPrepare()); assertLocked("k"); tm.getTransaction().runCommit(false); assertNoLocks(); } public void testLocksOnPutData() throws Exception { LockTestData tl = lockTestData; Cache<String, String> cache = tl.cache; TransactionManager tm = tl.tm; tm.begin(); cache.putAll(Collections.singletonMap("k", "v")); assertEquals("v", cache.get("k")); final EmbeddedTransaction tx = ((EmbeddedTransactionManager) tm).getTransaction(); assertTrue(tx.runPrepare()); assertLocked("k"); tx.runCommit(false); assertNoLocks(); tm.begin(); assertEquals("v", cache.get("k")); assertNoLocks(); tm.commit(); assertNoLocks(); } public void testLocksOnEvict() throws Exception { LockTestData tl = lockTestData; Cache<String, String> cache = tl.cache; TransactionManager tm = tl.tm; // init some data cache.putAll(Collections.singletonMap("k", "v")); assertEquals("v", cache.get("k")); tm.begin(); cache.evict("k"); assertNotLocked("k"); tm.commit(); assertFalse(cache.containsKey("k")); assertNoLocks(); } public void testLocksOnRemoveNonexistent() throws Exception { LockTestData tl = lockTestData; Cache<String, String> cache = tl.cache; EmbeddedTransactionManager tm = tl.tm; assert !cache.containsKey("k") : "Should not exist"; tm.begin(); cache.remove("k"); tm.getTransaction().runPrepare(); assertLocked("k"); tm.getTransaction().runCommit(false); assert !cache.containsKey("k") : "Should not exist"; assertNoLocks(); } public void testLocksOnEvictNonexistent() throws Exception { LockTestData tl = lockTestData; Cache<String, String> cache = tl.cache; TransactionManager tm = tl.tm; assert !cache.containsKey("k") : "Should not exist"; tm.begin(); cache.evict("k"); assertNotLocked("k"); tm.commit(); assert !cache.containsKey("k") : "Should not exist"; assertNoLocks(); } public void testLocksOnRemoveData() throws Exception { LockTestData tl = lockTestData; Cache<String, String> cache = tl.cache; EmbeddedTransactionManager tm = tl.tm; // init some data cache.put("k", "v"); cache.put("k2", "v2"); assertEquals("v", cache.get("k")); assertEquals("v2", cache.get("k2")); // remove tm.begin(); cache.remove("k"); cache.remove("k2"); assertTrue(tm.getTransaction().runPrepare()); assertLocked("k"); assertLocked("k2"); tm.getTransaction().runCommit(false); assert cache.isEmpty(); assertNoLocks(); } public void testWriteDoesntBlockRead() throws Exception { LockTestData tl = lockTestData; Cache<String, String> cache = tl.cache; TransactionManager tm = tl.tm; cache.put("k", "v"); // start a write. tm.begin(); cache.put("k2", "v2"); Transaction write = tm.suspend(); // now start a read and confirm that the write doesn't block it. tm.begin(); assertEquals("v", cache.get("k")); assert null == cache.get("k2") : "Should not see uncommitted changes"; Transaction read = tm.suspend(); // commit the write tm.resume(write); tm.commit(); assertNoLocks(); tm.resume(read); String value = cache.get("k2"); if (repeatableRead) { assert null == value : "Should have repeatable read"; } else // no guarantees with read committed assertTrue(null == value || "v2".equals(value)); tm.commit(); assertNoLocks(); } public void testUpdateDoesntBlockRead() throws Exception { LockTestData tl = lockTestData; Cache<String, String> cache = tl.cache; TransactionManager tm = tl.tm; cache.put("k", "v"); // Change K tm.begin(); cache.put("k", "v2"); Transaction write = tm.suspend(); // now start a read and confirm that the write doesn't block it. tm.begin(); assertEquals("v", cache.get("k")); Transaction read = tm.suspend(); // commit the write tm.resume(write); tm.commit(); assertNoLocks(); tm.resume(read); if (repeatableRead) assertEquals("Should have repeatable read", "v", cache.get("k")); else assertEquals("Read committed should see committed changes", "v2", cache.get("k")); tm.commit(); assertNoLocks(); } public void testWriteDoesntBlockReadNonexistent() throws Exception { LockTestData tl = lockTestData; Cache<String, String> cache = tl.cache; TransactionManager tm = tl.tm; // start a write. tm.begin(); cache.put("k", "v"); Transaction write = tm.suspend(); // now start a read and confirm that the write doesn't block it. tm.begin(); assert null == cache.get("k") : "Should not see uncommitted changes"; Transaction read = tm.suspend(); // commit the write tm.resume(write); tm.commit(); assertNoLocks(); tm.resume(read); String value = cache.get("k"); if (repeatableRead) { assert null == value : "Should have repeatable read"; } else { // no guarantees with read committed assertTrue(null == value || "v".equals(value)); } tm.commit(); assertNoLocks(); } public void testConcurrentWriters() throws Exception { LockTestData tl = lockTestData; Cache<String, String> cache = tl.cache; EmbeddedTransactionManager tm = tl.tm; tm.begin(); cache.put("k", "v"); final EmbeddedTransaction transaction = tm.getTransaction(); assertTrue(transaction.runPrepare()); tm.suspend(); tm.begin(); cache.put("k", "v"); assert !tm.getTransaction().runPrepare(); tm.rollback(); tm.resume(transaction); transaction.runCommit(false); assertNoLocks(); } public void testRollbacks() throws Exception { LockTestData tl = lockTestData; Cache<String, String> cache = tl.cache; TransactionManager tm = tl.tm; cache.put("k", "v"); tm.begin(); assertEquals("v", cache.get("k")); Transaction reader = tm.suspend(); tm.begin(); cache.put("k", "v2"); tm.rollback(); tm.resume(reader); Object value = cache.get("k"); assertEquals("v", value); tm.commit(); // even after commit assertEquals("v", cache.get("k")); assertNoLocks(); } public void testRollbacksOnNullEntry() throws Exception { LockTestData tl = lockTestData; Cache<String, String> cache = tl.cache; TransactionManager tm = tl.tm; tm.begin(); assert null == cache.get("k"); Transaction reader = tm.suspend(); tm.begin(); cache.put("k", "v"); assertEquals("v", cache.get("k")); tm.rollback(); tm.resume(reader); assert null == cache.get("k") : "Expecting null but was " + cache.get("k"); tm.commit(); // even after commit assert null == cache.get("k"); assertNoLocks(); } }