package org.infinispan.statetransfer; import static org.infinispan.test.TestingUtil.wrapInboundInvocationHandler; import static org.infinispan.test.TestingUtil.wrapComponent; import static org.testng.AssertJUnit.assertEquals; import java.util.Map; import java.util.concurrent.Future; import java.util.concurrent.TimeUnit; import java.util.concurrent.TimeoutException; import org.infinispan.commands.ReplicableCommand; import org.infinispan.commands.remote.CacheRpcCommand; import org.infinispan.commands.tx.PrepareCommand; import org.infinispan.commons.CacheException; import org.infinispan.configuration.cache.CacheMode; import org.infinispan.configuration.cache.ConfigurationBuilder; import org.infinispan.distribution.MagicKey; import org.infinispan.remoting.inboundhandler.AbstractDelegatingHandler; import org.infinispan.remoting.inboundhandler.DeliverOrder; import org.infinispan.remoting.inboundhandler.PerCacheInboundInvocationHandler; import org.infinispan.remoting.inboundhandler.Reply; import org.infinispan.remoting.responses.Response; import org.infinispan.remoting.responses.UnsureResponse; import org.infinispan.remoting.rpc.RpcManager; import org.infinispan.remoting.transport.Address; import org.infinispan.test.MultipleCacheManagersTest; import org.infinispan.test.concurrent.StateSequencer; import org.infinispan.transaction.lookup.EmbeddedTransactionManagerLookup; import org.infinispan.util.AbstractControlledRpcManager; import org.infinispan.util.ControlledConsistentHashFactory; import org.infinispan.util.concurrent.IsolationLevel; import org.infinispan.util.logging.Log; import org.infinispan.util.logging.LogFactory; import org.testng.annotations.Test; /** * Test for https://issues.jboss.org/browse/ISPN-6047 * * @author Pedro Ruivo * @since 8.2 */ @Test(groups = "functional", testName = "statetransfer.TxReplay3Test") public class TxReplay3Test extends MultipleCacheManagersTest { private static final String VALUE_1 = "v1"; private static final String VALUE_2 = "v2"; private static final String TX1_LOCKED = "tx1:acquired_lock"; private static final String TX1_UNSURE = "tx1:unsure_response"; private static final String TX2_PENDING = "tx2:waiting_tx1"; private static final String MAIN_ADVANCE = "main:advance"; private static final String JOIN_NEW_NODE = "join:add_new_node"; public void testReplay() throws Exception { final Object key = new MagicKey("TxReplay3Test", cache(0)); final StateSequencer sequencer = new StateSequencer(); sequencer.logicalThread("tx1", TX1_LOCKED, TX1_UNSURE); sequencer.logicalThread("tx2", TX2_PENDING); sequencer.logicalThread("join", JOIN_NEW_NODE); sequencer.logicalThread("main", MAIN_ADVANCE); sequencer.order(TX1_LOCKED, MAIN_ADVANCE, TX2_PENDING, JOIN_NEW_NODE, TX1_UNSURE); wrapComponent(cache(1), RpcManager.class, (wrapOn, current) -> new UnsureResponseRpcManager(current, sequencer), true); Handler handler = wrapInboundInvocationHandler(cache(0), current -> new Handler(current, sequencer)); handler.setOrigin(address(cache(2))); Future<Void> tx1 = fork(() -> { cache(1).put(key, VALUE_1); return null; }); sequencer.advance(MAIN_ADVANCE); Future<Void> tx2 = fork(() -> { cache(2).put(key, VALUE_2); return null; }); sequencer.enter(JOIN_NEW_NODE); addClusterEnabledCacheManager(config()).getCache(); waitForClusterToForm(); sequencer.exit(JOIN_NEW_NODE); tx1.get(30, TimeUnit.SECONDS); tx2.get(30, TimeUnit.SECONDS); assertEquals(VALUE_2, cache(0).get(key)); } @Override protected void createCacheManagers() throws Throwable { createClusteredCaches(3, config()); } private static ConfigurationBuilder config() { ConfigurationBuilder builder = getDefaultClusteredCacheConfig(CacheMode.DIST_SYNC, true); builder.transaction() .useSynchronization(false) .transactionManagerLookup(new EmbeddedTransactionManagerLookup()) .recovery().disable(); builder.locking().lockAcquisitionTimeout(1, TimeUnit.MINUTES).isolationLevel(IsolationLevel.READ_COMMITTED); builder.clustering() .remoteTimeout(1, TimeUnit.MINUTES) .hash().numOwners(1).numSegments(1) .consistentHashFactory(new ControlledConsistentHashFactory(0)) .stateTransfer().fetchInMemoryState(false); return builder; } private static class UnsureResponseRpcManager extends AbstractControlledRpcManager { private static final Log log = LogFactory.getLog(UnsureResponseRpcManager.class); private final StateSequencer sequencer; private volatile boolean triggered = false; public UnsureResponseRpcManager(RpcManager realOne, StateSequencer sequencer) { super(realOne); this.sequencer = sequencer; } @Override protected Object beforeInvokeRemotely(ReplicableCommand command) { Object arg = super.beforeInvokeRemotely(command); log.debugf("Before invoke remotely %s", command); return arg; } @Override protected Map<Address, Response> afterInvokeRemotely(ReplicableCommand command, Map<Address, Response> responseMap, Object argument) { Map<Address, Response> result = super.afterInvokeRemotely(command, responseMap, argument); log.debugf("After invoke remotely %s. Responses=%s", command, result); if (!triggered && command instanceof PrepareCommand) { log.debugf("Triggering %s and %s", TX1_LOCKED, TX1_UNSURE); triggered = true; try { sequencer.advance(TX1_LOCKED); sequencer.advance(TX1_UNSURE); } catch (TimeoutException | InterruptedException e) { throw new CacheException(e); } for (Map.Entry<Address, Response> entry : result.entrySet()) { entry.setValue(UnsureResponse.INSTANCE); } log.debugf("After invoke remotely %s. New Responses=%s", command, result); } return result; } } private static class Handler extends AbstractDelegatingHandler { private static final Log log = LogFactory.getLog(Handler.class); private final StateSequencer sequencer; private volatile boolean triggered = false; private volatile Address origin; public Handler(PerCacheInboundInvocationHandler delegate, StateSequencer sequencer) { super(delegate); this.sequencer = sequencer; } public void setOrigin(Address origin) { this.origin = origin; } @Override protected boolean beforeHandle(CacheRpcCommand command, Reply reply, DeliverOrder order) { log.debugf("Before invoking %s. expected origin=%s", command, origin); return super.beforeHandle(command, reply, order); } @Override protected void afterHandle(CacheRpcCommand command, DeliverOrder order, boolean delegated) { super.afterHandle(command, order, delegated); log.debugf("After invoking %s. expected origin=%s", command, origin); if (!triggered && command instanceof PrepareCommand && command.getOrigin().equals(origin)) { log.debugf("Triggering %s.", TX2_PENDING); triggered = true; try { sequencer.advance(TX2_PENDING); } catch (TimeoutException | InterruptedException e) { throw new CacheException(e); } } } } }