package org.infinispan.notifications.cachelistener.cluster; import static org.testng.Assert.assertEquals; import java.util.concurrent.Callable; import java.util.concurrent.CountDownLatch; import java.util.concurrent.ExecutionException; import java.util.concurrent.Future; import java.util.concurrent.ThreadLocalRandom; import java.util.concurrent.atomic.AtomicInteger; import org.infinispan.Cache; import org.infinispan.commons.executors.BlockingThreadPoolExecutorFactory; import org.infinispan.configuration.cache.CacheMode; import org.infinispan.configuration.cache.Configuration; 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.notifications.Listener; import org.infinispan.notifications.cachelistener.annotation.CacheEntryCreated; import org.infinispan.notifications.cachelistener.annotation.CacheEntryModified; import org.infinispan.notifications.cachelistener.annotation.CacheEntryRemoved; import org.infinispan.notifications.cachelistener.event.CacheEntryEvent; import org.infinispan.test.MultipleCacheManagersTest; import org.infinispan.test.fwk.TestResourceTracker; import org.testng.annotations.Test; /** * Stress test that simultates multiple writers to different cache nodes to verify cluster listener is notified * properly. * * @author wburns * @since 7.0 */ @Test(groups = "stress", testName = "notifications.cachelistener.cluster.ClusterListenerStressTest", timeOut = 15*60*1000) public class ClusterListenerStressTest extends MultipleCacheManagersTest { protected final static String CACHE_NAME = "cluster-listener"; protected final static String KEY = "ClusterListenerStressTestKey"; private static final int NUM_NODES = 3; protected ConfigurationBuilder builderUsed; @Override protected void createCacheManagers() throws Throwable { Configuration distConfig = getDefaultClusteredCacheConfig(CacheMode.DIST_SYNC, false).build(); for (int i = 0; i < NUM_NODES; i++) { GlobalConfigurationBuilder gcb = new GlobalConfigurationBuilder(); gcb.globalJmxStatistics().allowDuplicateDomains(true); gcb.transport().defaultTransport().nodeName(TestResourceTracker.getNameForIndex(i)); BlockingThreadPoolExecutorFactory remoteExecutorFactory = new BlockingThreadPoolExecutorFactory( 10, 1, 0, 60000); gcb.transport().remoteCommandThreadPool().threadPoolFactory(remoteExecutorFactory); EmbeddedCacheManager cm = new DefaultCacheManager(gcb.build()); registerCacheManager(cm); cm.defineConfiguration(CACHE_NAME, distConfig); log.infof("Started cache manager %s", cm.getAddress()); } waitForClusterToForm(CACHE_NAME); } @Listener(clustered = true) private static class ClusterListenerAggregator { AtomicInteger creationCount = new AtomicInteger(); AtomicInteger modifyCount = new AtomicInteger(); AtomicInteger removalCount = new AtomicInteger(); @CacheEntryCreated public void listenForModifications(CacheEntryEvent<String, Integer> event) { creationCount.incrementAndGet(); } @CacheEntryModified public void modified(CacheEntryEvent<String, Integer> event) { modifyCount.incrementAndGet(); } @CacheEntryRemoved public void removed(CacheEntryEvent<String, Integer> event) { removalCount.incrementAndGet(); } } private static class CreateModifyRemovals { private final int creationCount; private final int modifyCount; private final int removalCount; public CreateModifyRemovals(int creationCount, int modifyCount, int removalCount) { this.creationCount = creationCount; this.modifyCount = modifyCount; this.removalCount = removalCount; } } @Test public void runStressTestMultipleWriters() throws ExecutionException, InterruptedException { Cache<String, Integer> cache0 = cache(0, CACHE_NAME); Cache<String, Integer> cache1 = cache(1, CACHE_NAME); Cache<String, Integer> cache2 = cache(2, CACHE_NAME); ClusterListenerAggregator listener = new ClusterListenerAggregator(); cache0.addListener(listener); cache0.addListener(listener); cache0.addListener(listener); cache1.addListener(listener); cache1.addListener(listener); cache2.addListener(listener); long begin = System.currentTimeMillis(); int threadCount = 10; final CountDownLatch latch = new CountDownLatch(threadCount); Callable<CreateModifyRemovals> callable = new Callable<CreateModifyRemovals>() { @Override public CreateModifyRemovals call() throws Exception { latch.countDown(); latch.await(); int creationCount = 0; int modifyCount = 0; int removalCount = 0; for (int i = 0; i < 1000; i++) { int random = ThreadLocalRandom.current().nextInt(0, 23); boolean key = (random & 1) == 1; int cache = random / 8; int operation = random & 3; Cache<String, Integer> cacheToUse = cache(cache, CACHE_NAME); String keyToUse = key ? KEY : KEY + "2"; // 0 - regular put operation (detects if create modify) // 1 - remove operation // 2 - conditional replace/putIfAbsent // 3 - conditional remove switch (operation) { case 0: Integer prevValue = cacheToUse.put(keyToUse, i); if (prevValue != null) { modifyCount++; } else { creationCount++; } break; case 1: cacheToUse.remove(keyToUse); removalCount++; break; case 2: prevValue = cacheToUse.get(keyToUse); if (prevValue != null) { if (cacheToUse.replace(keyToUse, prevValue, i)) { modifyCount++; } } else { if (cacheToUse.putIfAbsent(keyToUse, i) == null) { creationCount++; } } break; case 3: prevValue = cacheToUse.get(keyToUse); if (prevValue != null) { cacheToUse.remove(keyToUse, prevValue); // We always notify on a removal even if it didn't remove it! removalCount++; } break; default: throw new IllegalArgumentException("Unsupported case!, provided " + operation); } } return new CreateModifyRemovals(creationCount, modifyCount, removalCount); } }; Future<CreateModifyRemovals>[] futures = new Future[threadCount]; for (int i = 0; i < threadCount; ++i) { futures[i] = fork(callable); } int creationCount = 0; int modifyCount = 0; int removalCount = 0; for (Future<CreateModifyRemovals> future : futures) { CreateModifyRemovals cmr = future.get(); creationCount += cmr.creationCount; modifyCount += cmr.modifyCount; removalCount += cmr.removalCount; } int listenerCount = 6; assertEquals(listener.creationCount.get(), creationCount * listenerCount); assertEquals(listener.modifyCount.get(), modifyCount * listenerCount); assertEquals(listener.removalCount.get(), removalCount * listenerCount); System.out.println("Took " + (System.currentTimeMillis() - begin) + " milliseconds"); } }