package org.infinispan.api; import static org.testng.AssertJUnit.assertEquals; import static org.testng.AssertJUnit.fail; import javax.transaction.RollbackException; import javax.transaction.Transaction; import javax.transaction.TransactionManager; import org.infinispan.Cache; import org.infinispan.configuration.cache.CacheMode; import org.infinispan.configuration.cache.ConfigurationBuilder; import org.infinispan.distribution.MagicKey; import org.infinispan.test.Exceptions; import org.infinispan.test.MultipleCacheManagersTest; import org.infinispan.transaction.LockingMode; import org.infinispan.util.concurrent.IsolationLevel; import org.testng.annotations.Test; /** * @author Pedro Ruivo * @since 6.0 */ @Test(groups = "functional") public abstract class AbstractRepeatableReadIsolationTest extends MultipleCacheManagersTest { private static final String INITIAL_VALUE = "init"; private static final String FINAL_VALUE = "final"; private static final String OTHER_VALUE = "other"; private final CacheMode cacheMode; private final LockingMode lockingMode; protected AbstractRepeatableReadIsolationTest(CacheMode cacheMode, LockingMode lockingMode) { this.cacheMode = cacheMode; this.lockingMode = lockingMode; } public void testPutTxIsolationInOwnerWithKeyInitialized() throws Exception { doIsolationTest(true, true, Operation.PUT); } public void testPutTxIsolationInOwnerWithKeyNoInitialized() throws Exception { doIsolationTest(true, false, Operation.PUT); } public void testPutTxIsolationInNonOwnerWithKeyInitialized() throws Exception { doIsolationTest(false, true, Operation.PUT); } public void testPutTxIsolationInNonOwnerWithKeyNonInitialized() throws Exception { doIsolationTest(false, false, Operation.PUT); } public void testRemoveTxIsolationInOwnerWithKeyInitialized() throws Exception { doIsolationTest(true, true, Operation.REMOVE); } public void testRemoveTxIsolationInOwnerWithKeyNoInitialized() throws Exception { doIsolationTest(true, false, Operation.REMOVE); } public void testRemoveTxIsolationInNonOwnerWithKeyInitialized() throws Exception { doIsolationTest(false, true, Operation.REMOVE); } public void testRemoveTxIsolationInNonOwnerWithKeyNonInitialized() throws Exception { doIsolationTest(false, false, Operation.REMOVE); } public void testReplaceTxIsolationInOwnerWithKeyInitialized() throws Exception { doIsolationTest(true, true, Operation.REPLACE); } public void testReplaceTxIsolationInOwnerWithKeyNoInitialized() throws Exception { doIsolationTest(true, false, Operation.REPLACE); } public void testReplaceTxIsolationInNonOwnerWithKeyInitialized() throws Exception { doIsolationTest(false, true, Operation.REPLACE); } public void testReplaceTxIsolationInNonOwnerWithKeyNonInitialized() throws Exception { doIsolationTest(false, false, Operation.REPLACE); } public void testConditionalPutTxIsolationInOwnerWithKeyInitialized() throws Exception { doIsolationTest(true, true, Operation.CONDITIONAL_PUT); } public void testConditionalPutTxIsolationInOwnerWithKeyNoInitialized() throws Exception { doIsolationTest(true, false, Operation.CONDITIONAL_PUT); } public void testConditionalPutTxIsolationInNonOwnerWithKeyInitialized() throws Exception { doIsolationTest(false, true, Operation.CONDITIONAL_PUT); } public void testConditionalPutTxIsolationInNonOwnerWithKeyNonInitialized() throws Exception { doIsolationTest(false, false, Operation.CONDITIONAL_PUT); } public void testConditionalRemoveTxIsolationInOwnerWithKeyInitialized() throws Exception { doIsolationTest(true, true, Operation.CONDITIONAL_REMOVE); } public void testConditionalRemoveTxIsolationInOwnerWithKeyNoInitialized() throws Exception { doIsolationTest(true, false, Operation.CONDITIONAL_REMOVE); } public void testConditionalRemoveTxIsolationInNonOwnerWithKeyInitialized() throws Exception { doIsolationTest(false, true, Operation.CONDITIONAL_REMOVE); } public void testConditionalRemoveTxIsolationInNonOwnerWithKeyNonInitialized() throws Exception { doIsolationTest(false, false, Operation.CONDITIONAL_REMOVE); } public void testConditionalReplaceTxIsolationInOwnerWithKeyInitialized() throws Exception { doIsolationTest(true, true, Operation.CONDITIONAL_REPLACE); } public void testConditionalReplaceTxIsolationInOwnerWithKeyNoInitialized() throws Exception { doIsolationTest(true, false, Operation.CONDITIONAL_REPLACE); } public void testConditionalReplaceTxIsolationInNonOwnerWithKeyInitialized() throws Exception { doIsolationTest(false, true, Operation.CONDITIONAL_REPLACE); } public void testConditionalReplaceTxIsolationInNonOwnerWithKeyNonInitialized() throws Exception { doIsolationTest(false, false, Operation.CONDITIONAL_REPLACE); } public void testPutTxIsolationAfterRemoveInOwner() throws Exception { doIsolationTestAfterRemove(true, Operation.PUT); } public void testPutTxIsolationAfterRemoveInNonOwner() throws Exception { doIsolationTestAfterRemove(false, Operation.PUT); } public void testRemoveTxIsolationAfterRemoveInOwner() throws Exception { doIsolationTestAfterRemove(true, Operation.REMOVE); } public void testRemoveTxIsolationAfterRemoveInNonOwner() throws Exception { doIsolationTestAfterRemove(false, Operation.REMOVE); } public void testReplaceTxIsolationAfterRemoveInOwner() throws Exception { doIsolationTestAfterRemove(true, Operation.REPLACE); } public void testReplaceTxIsolationAfterRemoveInNonOwner() throws Exception { doIsolationTestAfterRemove(false, Operation.REPLACE); } public void testConditionalPutTxIsolationAfterRemoveInOwner() throws Exception { doIsolationTestAfterRemove(true, Operation.CONDITIONAL_PUT); } public void testConditionalPutTxIsolationAfterRemoveInNonOwner() throws Exception { doIsolationTestAfterRemove(false, Operation.CONDITIONAL_PUT); } public void testConditionalRemoveTxIsolationAfterRemoveInOwner() throws Exception { doIsolationTestAfterRemove(true, Operation.CONDITIONAL_REMOVE); } public void testConditionalRemoveTxIsolationAfterRemoveInNonOwner() throws Exception { doIsolationTestAfterRemove(false, Operation.CONDITIONAL_REMOVE); } public void testConditionalReplaceTxIsolationAfterRemoveInOwner() throws Exception { doIsolationTestAfterRemove(true, Operation.CONDITIONAL_REPLACE); } public void testConditionalReplaceTxIsolationAfterRemoveInNonOwner() throws Exception { doIsolationTestAfterRemove(false, Operation.CONDITIONAL_REPLACE); } @Override protected void createCacheManagers() throws Throwable { ConfigurationBuilder builder = getDefaultClusteredCacheConfig(cacheMode, true); builder.locking().isolationLevel(IsolationLevel.REPEATABLE_READ); builder.transaction().lockingMode(lockingMode); builder.clustering().hash().numOwners(1); createClusteredCaches(2, builder); } private void doIsolationTest(boolean executeOnOwner, boolean initialized, Operation operation) throws Exception { final Cache<Object, Object> ownerCache = cache(0); final Object key = new MagicKey("shared", ownerCache); final Cache<Object, Object> cache = executeOnOwner ? cache(0) : cache(1); final TransactionManager tm = executeOnOwner ? tm(0) : tm(1); assertValueInAllCaches(key, null); final Object initValue = initialized ? INITIAL_VALUE : null; if (initialized) { ownerCache.put(key, initValue); assertValueInAllCaches(key, initValue); } tm.begin(); assertEquals("Wrong first get.", initValue, cache.get(key)); Transaction tx = tm.suspend(); ownerCache.put(key, OTHER_VALUE); assertValueInAllCaches(key, OTHER_VALUE); Object finalValueExpected = null; boolean commitFails = false; tm.resume(tx); assertEquals("Wrong second get.", initValue, cache.get(key)); switch (operation) { case PUT: finalValueExpected = FINAL_VALUE; commitFails = lockingMode == LockingMode.OPTIMISTIC; assertEquals("Wrong put return value.", initValue, cache.put(key, FINAL_VALUE)); assertEquals("Wrong final get.", FINAL_VALUE, cache.get(key)); break; case REMOVE: finalValueExpected = null; commitFails = lockingMode == LockingMode.OPTIMISTIC; assertEquals("Wrong remove return value.", initValue, cache.remove(key)); assertEquals("Wrong final get.", null, cache.get(key)); break; case REPLACE: finalValueExpected = initialized ? FINAL_VALUE : OTHER_VALUE; commitFails = lockingMode == LockingMode.OPTIMISTIC && initialized; assertEquals("Wrong replace return value.", initValue, cache.replace(key, FINAL_VALUE)); assertEquals("Wrong final get.", initialized ? FINAL_VALUE : null, cache.get(key)); break; case CONDITIONAL_PUT: finalValueExpected = initialized ? OTHER_VALUE : FINAL_VALUE; commitFails = lockingMode == LockingMode.OPTIMISTIC && !initialized; assertEquals("Wrong put return value.", initialized ? initValue : null, cache.putIfAbsent(key, FINAL_VALUE)); assertEquals("Wrong final get.", initialized ? initValue : FINAL_VALUE, cache.get(key)); break; case CONDITIONAL_REMOVE: finalValueExpected = initialized ? null : OTHER_VALUE; commitFails = lockingMode == LockingMode.OPTIMISTIC && initialized; assertEquals("Wrong remove return value.", initialized, cache.remove(key, INITIAL_VALUE)); assertEquals("Wrong final get.", null, cache.get(key)); break; case CONDITIONAL_REPLACE: finalValueExpected = initialized ? FINAL_VALUE : OTHER_VALUE; commitFails = lockingMode == LockingMode.OPTIMISTIC && initialized; assertEquals("Wrong replace return value.", initialized, cache.replace(key, INITIAL_VALUE, FINAL_VALUE)); assertEquals("Wrong final get.", initialized ? FINAL_VALUE : null, cache.get(key)); break; default: fail("Unknown operation " + operation); break; } if (commitFails) { Exceptions.expectException(RollbackException.class, tm::commit); } else { tm.commit(); } assertValueInAllCaches(key, lockingMode == LockingMode.PESSIMISTIC ? finalValueExpected : OTHER_VALUE); assertNoTransactions(); } private void doIsolationTestAfterRemove(boolean executeOnOwner, Operation operation) throws Exception { final Cache<Object, Object> ownerCache = cache(0); final Object key = new MagicKey("shared", ownerCache); final Cache<Object, Object> cache = executeOnOwner ? cache(0) : cache(1); final TransactionManager tm = executeOnOwner ? tm(0) : tm(1); assertValueInAllCaches(key, null); final Object initValue = INITIAL_VALUE; ownerCache.put(key, initValue); assertValueInAllCaches(key, initValue); tm.begin(); assertEquals("Wrong first get.", initValue, cache.get(key)); Transaction tx = tm.suspend(); ownerCache.put(key, OTHER_VALUE); assertValueInAllCaches(key, OTHER_VALUE); Object finalValueExpected = null; boolean commitFails = lockingMode == LockingMode.OPTIMISTIC; tm.resume(tx); assertEquals("Wrong second get.", initValue, cache.get(key)); assertEquals("Wrong value after remove.", initValue, cache.remove(key)); switch (operation) { case PUT: finalValueExpected = FINAL_VALUE; assertEquals("Wrong put return value.", null, cache.put(key, FINAL_VALUE)); assertEquals("Wrong final get.", FINAL_VALUE, cache.get(key)); break; case REMOVE: finalValueExpected = null; assertEquals("Wrong remove return value.", null, cache.remove(key)); assertEquals("Wrong final get.", null, cache.get(key)); break; case REPLACE: finalValueExpected = null; assertEquals("Wrong replace return value.", null, cache.replace(key, FINAL_VALUE)); assertEquals("Wrong final get.", null, cache.get(key)); break; case CONDITIONAL_PUT: finalValueExpected = FINAL_VALUE; assertEquals("Wrong put return value.", null, cache.putIfAbsent(key, FINAL_VALUE)); assertEquals("Wrong final get.", FINAL_VALUE, cache.get(key)); break; case CONDITIONAL_REMOVE: finalValueExpected = null; assertEquals("Wrong remove return value.", false, cache.remove(key, INITIAL_VALUE)); assertEquals("Wrong final get.", null, cache.get(key)); break; case CONDITIONAL_REPLACE: finalValueExpected = null; assertEquals("Wrong replace return value.", false, cache.replace(key, INITIAL_VALUE, FINAL_VALUE)); assertEquals("Wrong final get.", null, cache.get(key)); break; default: fail("Unknown operation " + operation); break; } if (commitFails) { Exceptions.expectException(RollbackException.class, tm::commit); } else { tm.commit(); } assertValueInAllCaches(key, lockingMode == LockingMode.PESSIMISTIC ? finalValueExpected : OTHER_VALUE); assertNoTransactions(); } private void assertValueInAllCaches(final Object key, final Object value) { for (Cache<Object, Object> cache : caches()) { assertEquals("Wrong value.", value, cache.get(key)); } } private enum Operation { PUT, REMOVE, REPLACE, CONDITIONAL_PUT, CONDITIONAL_REMOVE, CONDITIONAL_REPLACE } }