package org.infinispan.atomic; import java.util.ArrayList; import java.util.Arrays; import java.util.Collection; import java.util.HashSet; import java.util.Set; import java.util.concurrent.Callable; import java.util.concurrent.Future; import javax.transaction.Transaction; import org.infinispan.commands.tx.PrepareCommand; import org.infinispan.commands.write.ApplyDeltaCommand; import org.infinispan.configuration.cache.CacheMode; import org.infinispan.configuration.cache.ConfigurationBuilder; import org.infinispan.context.InvocationContext; import org.infinispan.distribution.DistributionTestHelper; import org.infinispan.distribution.MagicKey; import org.infinispan.interceptors.base.BaseCustomInterceptor; import org.infinispan.interceptors.impl.TxInterceptor; import org.infinispan.remoting.rpc.RpcManager; import org.infinispan.test.MultipleCacheManagersTest; import org.infinispan.test.TestingUtil; import org.infinispan.transaction.LockingMode; import org.infinispan.tx.dld.ControlledRpcManager; import org.infinispan.util.concurrent.locks.LockManager; import org.testng.Assert; import org.testng.annotations.Test; /** * @author Pedro Ruivo * @since 5.3 */ @Test(groups = "functional") public abstract class BaseAtomicMapLockingTest extends MultipleCacheManagersTest { private static final int NUM_NODES = 3; private static final String VALUE = "value"; private static final Object[] EMPTY_ARRAY = new Object[0]; private final boolean pessimistic; private final CollectCompositeKeysInterceptor[] collectors = new CollectCompositeKeysInterceptor[NUM_NODES]; private final ControlledRpcManager[] rpcManagers = new ControlledRpcManager[NUM_NODES]; private Object ahmKey; private Object fgahmKey; protected BaseAtomicMapLockingTest(boolean pessimistic) { this.pessimistic = pessimistic; } public final void testAtomicHasMapLockingOnLockOwner() throws Exception { testAtomicHashMap(true); } public final void testAtomicHasMapLockingOnNonLockOwner() throws Exception { testAtomicHashMap(false); } public final void testFineGrainedAtomicHashMapLockingOnLockOwner() throws Exception { testFineGrainedAtomicHashMap(true); } public final void testFineGrainedAtomicHashMapLockingOnNonLockOwner() throws Exception { testFineGrainedAtomicHashMap(false); } @Override protected void createCacheManagers() throws Throwable { for (int i = 0; i < NUM_NODES; ++i) { collectors[i] = new CollectCompositeKeysInterceptor(); ConfigurationBuilder builder = getDefaultClusteredCacheConfig(CacheMode.DIST_SYNC, true); builder.transaction().lockingMode(pessimistic ? LockingMode.PESSIMISTIC : LockingMode.OPTIMISTIC); builder.customInterceptors().addInterceptor().interceptor(collectors[i]) .before(TxInterceptor.class); builder.clustering().hash().numOwners(2); addClusterEnabledCacheManager(builder); } waitForClusterToForm(); for (int i = 0; i < NUM_NODES; ++i) { RpcManager rpcManager = TestingUtil.extractComponent(cache(i), RpcManager.class); rpcManagers[i] = new ControlledRpcManager(rpcManager); TestingUtil.replaceComponent(cache(i), RpcManager.class, rpcManagers[i], true); } ahmKey = new MagicKey("AtomicHashMap", cache(0)); fgahmKey = new MagicKey("FineGrainedAtomicHashMap", cache(0)); } protected final void testAtomicHashMap(boolean executeOnLockOwner) throws Exception { resetBeforeMethod(); final int txExecutor = executeOnLockOwner ? 0 : 1; AtomicMap<Object, Object> map = AtomicMapLookup.getAtomicMap(cache(txExecutor), ahmKey); tm(txExecutor).begin(); map.put("key1", VALUE); map.put("key2", VALUE); map.put("key3", VALUE); final Transaction tx1 = tm(txExecutor).suspend(); if (pessimistic) { rpcManagers[txExecutor].blockBefore(PrepareCommand.class); } else { rpcManagers[txExecutor].blockAfter(PrepareCommand.class); } Future<Boolean> txOutcome = fork(new Callable<Boolean>() { @Override public Boolean call() throws Exception { try { tm(txExecutor).resume(tx1); tm(txExecutor).commit(); return Boolean.TRUE; } catch (Exception e) { return Boolean.FALSE; } } }); try { rpcManagers[txExecutor].waitForCommandToBlock(); assertKeysLocked(0, ahmKey); assertKeysLocked(1, EMPTY_ARRAY); assertKeysLocked(2, EMPTY_ARRAY); rpcManagers[txExecutor].stopBlocking(); Assert.assertTrue(txOutcome.get()); } finally { rpcManagers[txExecutor].stopBlocking(); } } protected final void testFineGrainedAtomicHashMap(boolean executeOnLockOwner) throws Exception { resetBeforeMethod(); final int txExecutor = executeOnLockOwner ? 0 : 1; FineGrainedAtomicMap<Object, Object> map = AtomicMapLookup.getFineGrainedAtomicMap(cache(txExecutor), fgahmKey); boolean hasLocalKeys = false; boolean hasRemoteKeys = false; int keyIndex = 0; tm(txExecutor).begin(); while (!hasLocalKeys || !hasRemoteKeys) { map.put("key" + keyIndex++, VALUE); //has composite keys mapped to the lock owner? hasLocalKeys = hasKeyMappedTo(true, collectors[txExecutor].getCompositeKeys()); //has composite keys mapped to the non lock owner? hasRemoteKeys = hasKeyMappedTo(false, collectors[txExecutor].getCompositeKeys()); //the locks are independent on where the composite keys are mapped to. } final Transaction tx1 = tm(txExecutor).suspend(); Assert.assertEquals(collectors[txExecutor].getCompositeKeys().size(), keyIndex, "Wrong number of composite keys collected!"); log.infof("%s composite keys collected.", collectors[txExecutor].getCompositeKeys().size()); if (pessimistic) { rpcManagers[txExecutor].blockBefore(PrepareCommand.class); } else { rpcManagers[txExecutor].blockAfter(PrepareCommand.class); } Future<Boolean> txOutcome = fork(new Callable<Boolean>() { @Override public Boolean call() throws Exception { try { tm(txExecutor).resume(tx1); tm(txExecutor).commit(); return Boolean.TRUE; } catch (Exception e) { return Boolean.FALSE; } } }); try { rpcManagers[txExecutor].waitForCommandToBlock(); assertKeysLocked(0, collectors[txExecutor].getCompositeKeys().toArray()); assertKeysLocked(1, EMPTY_ARRAY); assertKeysLocked(2, EMPTY_ARRAY); rpcManagers[txExecutor].stopBlocking(); Assert.assertTrue(txOutcome.get()); } finally { rpcManagers[txExecutor].stopBlocking(); } } protected void assertKeysLocked(int index, Object... keys) { LockManager lockManager = lockManager(index); Assert.assertNotNull(keys); for (Object key : keys) { Assert.assertTrue(lockManager.isLocked(key), key + " is not locked in cache(" + index + ")."); } } protected boolean hasKeyMappedTo(boolean toLockOwner, Collection<Object> keys) { for (Object key : keys) { boolean onLockOwner = DistributionTestHelper.isFirstOwner(cache(0), key); if ((toLockOwner && onLockOwner) || (!toLockOwner && !onLockOwner)) { return true; } } return false; } private void resetBeforeMethod() { for (int i = 0; i < NUM_NODES; ++i) { if (collectors[i] != null) { collectors[i].reset(); } if (rpcManagers[i] != null) { rpcManagers[i].stopBlocking(); rpcManagers[i].stopFailing(); } } } private static class CollectCompositeKeysInterceptor extends BaseCustomInterceptor { private final Set<Object> compositeKeys; public CollectCompositeKeysInterceptor() { compositeKeys = new HashSet<Object>(); } @Override public Object visitApplyDeltaCommand(InvocationContext ctx, ApplyDeltaCommand command) throws Throwable { synchronized (compositeKeys) { compositeKeys.addAll(Arrays.asList(command.getCompositeKeys())); } return invokeNextInterceptor(ctx, command); } public final void reset() { synchronized (compositeKeys) { compositeKeys.clear(); } } public final Collection<Object> getCompositeKeys() { synchronized (compositeKeys) { return new ArrayList<Object>(compositeKeys); } } } }