package org.infinispan.statetransfer; import static org.infinispan.test.TestingUtil.blockUntilViewsReceived; import static org.infinispan.test.TestingUtil.waitForNoRebalance; import java.io.ByteArrayInputStream; 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.configuration.global.GlobalConfigurationBuilder; import org.infinispan.manager.DefaultCacheManager; import org.infinispan.manager.EmbeddedCacheManager; import org.infinispan.remoting.transport.jgroups.CommandAwareRpcDispatcher; import org.infinispan.test.MultipleCacheManagersTest; import org.infinispan.test.fwk.CleanupAfterMethod; import org.infinispan.test.fwk.JGroupsConfigBuilder; import org.infinispan.test.fwk.TestResourceTracker; import org.infinispan.test.fwk.TransportFlags; import org.jgroups.JChannel; import org.jgroups.Message; import org.jgroups.blocks.RequestCorrelator; import org.jgroups.conf.ClassConfigurator; import org.jgroups.fork.ForkChannel; import org.jgroups.fork.UnknownForkHandler; import org.jgroups.protocols.FORK; import org.testng.annotations.DataProvider; import org.testng.annotations.Test; /** * Tests concurrent startup of caches using ForkChannels. * * @author Dan Berindei * @since 9.0 */ @Test(testName = "statetransfer.ConcurrentStartForkChannelTest", groups = "functional") @CleanupAfterMethod public class ConcurrentStartForkChannelTest extends MultipleCacheManagersTest { public static final byte[] FORK_NOT_FOUND_BUFFER = new byte[0]; public static final String CACHE_NAME = "repl"; @Override protected void createCacheManagers() throws Throwable { // The test method will create the cache managers } @DataProvider(name = "startOrder") public Object[][] startOrder() { return new Object[][]{{0, 1}, {1, 0}}; } @Test(timeOut = 30000, dataProvider = "startOrder") public void testConcurrentStart(int eagerManager, int lazyManager) throws Exception { TestResourceTracker.testThreadStarted(this); ConfigurationBuilder replCfg = new ConfigurationBuilder(); replCfg.clustering().cacheMode(CacheMode.REPL_SYNC).stateTransfer().timeout(30, TimeUnit.SECONDS); String name1 = TestResourceTracker.getNextNodeName(); String name2 = TestResourceTracker.getNextNodeName(); // Create and connect both channels beforehand JChannel ch1 = createChannel(name1, 0); JChannel ch2 = createChannel(name2, 1); // Create the cache managers, but do not start them yet EmbeddedCacheManager cm1 = createCacheManager(replCfg, name1, ch1); EmbeddedCacheManager cm2 = createCacheManager(replCfg, name2, ch2); cm1.defineConfiguration(CACHE_NAME, replCfg.build()); cm2.defineConfiguration(CACHE_NAME, replCfg.build()); try { log.debugf("Cache managers created. Starting the caches"); // When the coordinator starts first, it's ok to just start the caches in sequence. // When the coordinator starts last, however, the other node is not able to start before the // coordinator has the ClusterTopologyManager running. Future<Cache<String, String>> c1rFuture = fork(() -> manager(eagerManager).getCache(CACHE_NAME)); Thread.sleep(1000); Cache<String, String> c2r = manager(lazyManager).getCache(CACHE_NAME); Cache<String, String> c1r = c1rFuture.get(10, TimeUnit.SECONDS); blockUntilViewsReceived(10000, cm1, cm2); waitForNoRebalance(c1r, c2r); } finally { // Stopping the cache managers isn't enough, because it will only close the ForkChannels cm1.stop(); ch1.close(); cm2.stop(); ch2.close(); } } private EmbeddedCacheManager createCacheManager(ConfigurationBuilder cacheCfg, String name, JChannel channel) throws Exception { GlobalConfigurationBuilder gcb = new GlobalConfigurationBuilder(); gcb.transport().nodeName(channel.getName()); gcb.globalJmxStatistics().allowDuplicateDomains(true); gcb.transport().distributedSyncTimeout(30, TimeUnit.SECONDS); FORK fork = new FORK(); fork.setUnknownForkHandler(new UnknownForkHandler() { @Override public Object handleUnknownForkStack(Message message, String forkStackId) { return handle(message); } @Override public Object handleUnknownForkChannel(Message message, String forkChannelId) { return handle(message); } private Object handle(Message message) { short id = ClassConfigurator.getProtocolId(RequestCorrelator.class); RequestCorrelator.Header header = message.getHeader(id); if (header != null) { log.debugf("Sending CacheNotFoundResponse reply for %s", header); short flags = (short) ((message.getFlags() | CommandAwareRpcDispatcher.REPLY_FLAGS_TO_SET) & ~CommandAwareRpcDispatcher.REPLY_FLAGS_TO_CLEAR); Message response = message.makeReply().setFlag(flags); response.putHeader(FORK.ID, message.getHeader(FORK.ID)); response.putHeader(id, new RequestCorrelator.Header(RequestCorrelator.Header.RSP, header.req_id, id)); response.setBuffer(FORK_NOT_FOUND_BUFFER); fork.down(response); } return null; } }); channel.getProtocolStack().addProtocol(fork); ForkChannel fch = new ForkChannel(channel, "stack1", "channel1"); CustomChannelLookup.registerChannel(gcb, fch, name, true); EmbeddedCacheManager cm = new DefaultCacheManager(gcb.build(), cacheCfg.build(), false); registerCacheManager(cm); return cm; } private JChannel createChannel(String name, int portRange) throws Exception { String configString = JGroupsConfigBuilder .getJGroupsConfig(ConcurrentStartForkChannelTest.class.getName(), new TransportFlags().withPortRange(portRange)); JChannel channel = new JChannel(new ByteArrayInputStream(configString.getBytes())); channel.setName(name); channel.connect(ConcurrentStartForkChannelTest.class.getSimpleName()); log.tracef("Channel %s connected: %s", channel, channel.getViewAsString()); return channel; } }