package org.infinispan.api; import java.util.concurrent.Callable; import java.util.concurrent.Future; import org.infinispan.Cache; import org.infinispan.commands.write.PutKeyValueCommand; import org.infinispan.commands.write.RemoveCommand; import org.infinispan.commands.write.ReplaceCommand; import org.infinispan.configuration.cache.CacheMode; import org.infinispan.configuration.cache.ConfigurationBuilder; import org.infinispan.remoting.rpc.RpcManager; import org.infinispan.test.MultipleCacheManagersTest; import org.infinispan.test.TestingUtil; import org.infinispan.tx.dld.ControlledRpcManager; import org.testng.AssertJUnit; import org.testng.annotations.Test; /** * Data inconsistency can happen in non-transactional caches. the tests replicates this scenario: assuming N1 and N2 are * owners of key K. N2 is the primary owner * <p/> * <ul> * <li>N1 tries to update K. it forwards the command to N2.</li> * <li>N2 acquires the lock, and forwards back to N1 (that applies the modification).</li> * <li>N2 releases the lock and replies to N1.</li> * <li>N1 applies again the modification without the lock.</li> * </ul> * * @author Pedro Ruivo * @since 6.0 */ @Test(groups = "functional", testName = "api.NonDuplicateModificationTest") public class NonDuplicateModificationTest extends MultipleCacheManagersTest { /** * ISPN-3354 */ public void testPut() throws Exception { performTestOn(Operation.PUT); } /** * ISPN-3354 */ public void testReplace() throws Exception { performTestOn(Operation.REPLACE); } /** * ISPN-3354 */ public void testRemove() throws Exception { performTestOn(Operation.REMOVE); } @Override protected void createCacheManagers() throws Throwable { ConfigurationBuilder builder = getDefaultClusteredCacheConfig(CacheMode.REPL_SYNC, false); builder.clustering().hash() .numSegments(60); createClusteredCaches(2, builder); } private void performTestOn(final Operation operation) throws Exception { final Object key = getKeyForCache(cache(0), cache(1)); final ControlledRpcManager controlledRpcManager = replaceRpcManager(cache(1)); cache(0).put(key, "v1"); operation.setCommandToBlock(controlledRpcManager); assertKeyValue(key, "v1"); Future<Void> future = fork(new Callable<Void>() { @Override public Void call() throws Exception { operation.execute(cache(1), key, "v2"); return null; } }); controlledRpcManager.waitForCommandToBlock(); cache(0).put(key, "v3"); controlledRpcManager.stopBlocking(); future.get(); assertKeyValue(key, "v3"); } private void assertKeyValue(Object key, Object expected) { for (Cache cache : caches()) { AssertJUnit.assertEquals("Wrong value for key " + key + " on " + address(cache), expected, cache.get(key)); } } private ControlledRpcManager replaceRpcManager(Cache cache) { RpcManager rpcManager = TestingUtil.extractComponent(cache, RpcManager.class); ControlledRpcManager controlledRpcManager = new ControlledRpcManager(rpcManager); TestingUtil.replaceComponent(cache, RpcManager.class, controlledRpcManager, true); return controlledRpcManager; } private enum Operation { PUT(PutKeyValueCommand.class), REMOVE(RemoveCommand.class), REPLACE(ReplaceCommand.class); private final Class<?> classToBlock; Operation(Class<?> classToBlock) { this.classToBlock = classToBlock; } private void setCommandToBlock(ControlledRpcManager rpcManager) { rpcManager.blockAfter(classToBlock); } private void execute(Cache<Object, Object> cache, Object key, Object value) { switch (this) { case PUT: cache.put(key, value); break; case REMOVE: cache.remove(key); break; case REPLACE: cache.replace(key, value); break; } } } }