package org.infinispan.statetransfer; import static org.testng.AssertJUnit.assertEquals; import java.util.concurrent.Future; import java.util.concurrent.TimeUnit; import org.infinispan.Cache; import org.infinispan.configuration.cache.CacheMode; import org.infinispan.configuration.cache.ConfigurationBuilder; import org.infinispan.test.MultipleCacheManagersTest; import org.infinispan.test.TestingUtil; import org.infinispan.test.fwk.TransportFlags; import org.infinispan.util.BlockingClusterTopologyManager; import org.infinispan.util.BlockingLocalTopologyManager; import org.infinispan.util.ControlledConsistentHashFactory; import org.testng.annotations.Test; @Test(groups = "unit", testName = "statetransfer.LeaveDuringStateTransferTest", description = "One instance of ISPN-5021") public class LeaveDuringStateTransferTest extends MultipleCacheManagersTest { private ControlledConsistentHashFactory factory = new ControlledConsistentHashFactory(0, 1); @Override protected void createCacheManagers() throws Throwable { ConfigurationBuilder cb = configuration(); createClusteredCaches(3, cb, new TransportFlags().withFD(true)); } private ConfigurationBuilder configuration() { ConfigurationBuilder cb = getDefaultClusteredCacheConfig(CacheMode.DIST_SYNC); cb.clustering().hash().numSegments(1).consistentHashFactory(factory); return cb; } public void test() throws Exception { BlockingClusterTopologyManager clusterTopologyManager = BlockingClusterTopologyManager.replace(cacheManagers.get(0)); BlockingLocalTopologyManager localTopologyManager0 = BlockingLocalTopologyManager.replaceTopologyManager(cacheManagers.get(0)); BlockingLocalTopologyManager localTopologyManager2 = BlockingLocalTopologyManager.replaceTopologyManager(cacheManagers.get(2)); int currentTopology = currentTopologyId(cache(0)); // block last CH_UPDATE in the state transfer BlockingClusterTopologyManager.Handle h3 = clusterTopologyManager.startBlockingTopologyConfirmations( topologyId -> topologyId >= currentTopology + 3); Future<?> joiner = null; try { factory.setOwnerIndexes(1, 2); addClusterEnabledCacheManager(configuration(), new TransportFlags().withFD(true)); joiner = fork(() -> cacheManagers.get(3).getCache()); h3.waitToBlock(); log.debug("State transfer almost complete"); eventually(() -> currentTopologyId(cache(2)) == currentTopology + 3); localTopologyManager2.startBlocking(BlockingLocalTopologyManager.LatchType.CONSISTENT_HASH_UPDATE); localTopologyManager2.startBlocking(BlockingLocalTopologyManager.LatchType.REBALANCE); // Block rebalance that could follow even if the previous rebalance was not completed localTopologyManager0.startBlocking(BlockingLocalTopologyManager.LatchType.REBALANCE); log.debug("Isolating node " + cacheManagers.get(1)); TestingUtil.getDiscardForCache(cache(1)).setDiscardAll(true); TestingUtil.blockUntilViewsReceived(60000, true, cacheManagers); log.debug("Waiting for topology update from view change"); // since we're blocking confirmation for topology +3, the updated topology will be +4 eventually(() -> currentTopologyId(cache(0)) >= currentTopology + 4); StateTransferLock originalLock = TestingUtil.extractComponent(cache(2), StateTransferLock.class); UnblockingStateTransferLock lock = new UnblockingStateTransferLock(originalLock, currentTopology + 4, localTopologyManager2); TestingUtil.replaceComponent(cache(2), StateTransferLock.class, lock, true); cache(0).put("key", "value"); assertEquals("value", cache(2).get("key")); } finally { h3.stopBlocking(); localTopologyManager2.stopBlocking(BlockingLocalTopologyManager.LatchType.CONSISTENT_HASH_UPDATE); localTopologyManager2.stopBlocking(BlockingLocalTopologyManager.LatchType.REBALANCE); localTopologyManager0.stopBlocking(BlockingLocalTopologyManager.LatchType.REBALANCE); if (joiner != null) joiner.get(10, TimeUnit.SECONDS); } } private int currentTopologyId(Cache cache) { return TestingUtil.extractComponent(cache, StateTransferManager.class).getCacheTopology().getTopologyId(); } private class UnblockingStateTransferLock extends DelegatingStateTransferLock { private final int unblockingTopology; private final BlockingLocalTopologyManager localTopologyManager; public UnblockingStateTransferLock(StateTransferLock delegate, int unblockingTopology, BlockingLocalTopologyManager localTopologyManager) { super(delegate); this.unblockingTopology = unblockingTopology; this.localTopologyManager = localTopologyManager; } @Override public boolean transactionDataReceived(int expectedTopologyId) { log.info("Requesting topology " + expectedTopologyId); if (expectedTopologyId == unblockingTopology) { localTopologyManager.stopBlocking(BlockingLocalTopologyManager.LatchType.CONSISTENT_HASH_UPDATE); } return super.transactionDataReceived(expectedTopologyId); } } }