package org.infinispan.interceptors.impl; import java.util.ArrayList; import java.util.Collections; import java.util.List; import java.util.concurrent.Callable; import java.util.concurrent.CyclicBarrier; import java.util.concurrent.ExecutorService; import java.util.concurrent.Executors; import java.util.concurrent.Future; import org.infinispan.commands.VisitableCommand; import org.infinispan.context.InvocationContext; import org.infinispan.factories.components.ComponentMetadataRepo; import org.infinispan.interceptors.AsyncInterceptor; import org.infinispan.interceptors.AsyncInterceptorChain; import org.infinispan.interceptors.BaseAsyncInterceptor; import org.infinispan.test.AbstractInfinispanTest; import org.infinispan.util.logging.Log; import org.infinispan.util.logging.LogFactory; import org.testng.annotations.Test; /** * Tests {@link AsyncInterceptorChainImpl} concurrent updates * * @author Galder ZamarreƱo * @author Sanne Grinovero * @author Dan Berindei * @since 9.0 */ @Test(groups = "functional", testName = "interceptors.AsyncInterceptorChainTest") public class AsyncInterceptorChainTest extends AbstractInfinispanTest { private static final Log log = LogFactory.getLog(AsyncInterceptorChainTest.class); public void testConcurrentAddRemove() throws Exception { ComponentMetadataRepo componentMetadataRepo = new ComponentMetadataRepo(); componentMetadataRepo.initialize(Collections.emptyList(), AsyncInterceptorChainTest.class.getClassLoader()); AsyncInterceptorChainImpl ic = new AsyncInterceptorChainImpl(componentMetadataRepo); ic.addInterceptor(new DummyCallInterceptor(), 0); ic.addInterceptor(new DummyActivationInterceptor(), 1); CyclicBarrier barrier = new CyclicBarrier(4); List<Future<Void>> futures = new ArrayList<>(2); ExecutorService executorService = Executors.newFixedThreadPool(3, getTestThreadFactory("Worker")); try { // We do test concurrent add/remove of different types per thread, // so that the final result is predictable (testable) and that we // can not possibly fail because of the InterceptorChain checking // that no interceptor is ever added twice. futures.add(executorService.submit(new InterceptorChainUpdater(ic, barrier, new DummyCacheMgmtInterceptor()))); futures.add(executorService.submit(new InterceptorChainUpdater(ic, barrier, new DummyDistCacheWriterInterceptor()))); futures.add(executorService.submit(new InterceptorChainUpdater(ic, barrier, new DummyInvalidationInterceptor()))); barrier.await(); // wait for all threads to be ready barrier.await(); // wait for all threads to finish log.debug("All threads finished, let's shutdown the executor and check whether any exceptions were reported"); for (Future<Void> future : futures) future.get(); } finally { executorService.shutdownNow(); } assert ic.containsInterceptorType(DummyCallInterceptor.class); assert ic.containsInterceptorType(DummyActivationInterceptor.class); assert ic.containsInterceptorType(DummyCacheMgmtInterceptor.class); assert ic.containsInterceptorType(DummyDistCacheWriterInterceptor.class); assert ic.containsInterceptorType(DummyInvalidationInterceptor.class); assert ic.getInterceptors().size() == 5 : "Resulting interceptor chain was actually " + ic.getInterceptors(); } private static class InterceptorChainUpdater implements Callable<Void> { private final AsyncInterceptorChain ic; private final CyclicBarrier barrier; private final AsyncInterceptor interceptor; InterceptorChainUpdater(AsyncInterceptorChain ic, CyclicBarrier barrier, AsyncInterceptor interceptor) { this.ic = ic; this.barrier = barrier; this.interceptor = interceptor; } @Override public Void call() throws Exception { final Class<? extends AsyncInterceptor> interceptorClass = interceptor.getClass(); try { log.debug("Wait for all executions paths to be ready to perform calls"); barrier.await(); // test in a loop as the barrier is otherwise not enough to make sure // the different testing threads actually do make changes concurrently // 2000 is still almost nothing in terms of testsuite time. for (int i = 0; i < 2000; i++) { ic.removeInterceptor(interceptorClass); ic.addInterceptor(interceptor, 1); } return null; } finally { log.debug("Wait for all execution paths to finish"); barrier.await(); } } } private static class DummyCallInterceptor extends BaseAsyncInterceptor { @Override public Object visitCommand(InvocationContext ctx, VisitableCommand command) throws Throwable { return null; } } private static class DummyActivationInterceptor extends DummyCallInterceptor { } private static class DummyCacheMgmtInterceptor extends DummyCallInterceptor { } private static class DummyDistCacheWriterInterceptor extends DummyCallInterceptor { } private static class DummyInvalidationInterceptor extends DummyCallInterceptor { } }