package org.infinispan.interceptors; import static org.infinispan.test.TestingUtil.withCacheManager; import java.util.concurrent.Callable; import java.util.concurrent.CountDownLatch; import java.util.concurrent.Future; import java.util.concurrent.TimeUnit; import org.infinispan.Cache; import org.infinispan.commands.write.PutKeyValueCommand; import org.infinispan.configuration.cache.ConfigurationBuilder; import org.infinispan.context.InvocationContext; import org.infinispan.interceptors.base.BaseCustomInterceptor; import org.infinispan.interceptors.impl.EntryWrappingInterceptor; import org.infinispan.test.AbstractInfinispanTest; import org.infinispan.test.CacheManagerCallable; import org.infinispan.test.TestingUtil; import org.infinispan.test.fwk.TestCacheManagerFactory; import org.infinispan.util.logging.Log; import org.infinispan.util.logging.LogFactory; import org.testng.annotations.Test; /** * Tests visibility of effects of cache operations on a separate thread once * they've passed a particular interceptor barrier related to the cache * operation. * * @author Galder ZamarreƱo * @since 5.1 */ @Test(groups = "functional", testName = "interceptors.ConcurrentInterceptorVisibilityTest") public class ConcurrentInterceptorVisibilityTest extends AbstractInfinispanTest { public void testSizeVisibility() throws Exception { updateCache(Visibility.SIZE); } @Test(groups = "unstable") public void testGetVisibility() throws Exception { updateCache(Visibility.GET); } private void updateCache(final Visibility visibility) throws Exception { final String key = "k-" + visibility; final String value = "k-" + visibility; final CountDownLatch entryCreatedLatch = new CountDownLatch(1); final EntryCreatedInterceptor interceptor = new EntryCreatedInterceptor(entryCreatedLatch); ConfigurationBuilder builder = new ConfigurationBuilder(); builder.customInterceptors().addInterceptor() .interceptor(interceptor) .before(EntryWrappingInterceptor.class); withCacheManager(new CacheManagerCallable( TestCacheManagerFactory.createCacheManager(builder)) { @Override public void call() throws Exception { final Cache<Object,Object> cache = cm.getCache(); switch (visibility) { case SIZE: assert cache.size() == 0; break; case GET: assert cache.get(key) == null; break; } Future<Void> ignore = ConcurrentInterceptorVisibilityTest .this.fork(new Callable<Void>() { @Override public Void call() throws Exception { cache.put(key, value); return null; } }); try { entryCreatedLatch.await(30, TimeUnit.SECONDS); } catch (InterruptedException e) { Thread.currentThread().interrupt(); } switch (visibility) { case SIZE: int size = cache.size(); assert size == 1 : "size is: " + size; assert interceptor.assertKeySet; break; case GET: Object retVal = cache.get(key); assert retVal != null; assert retVal.equals(value): "retVal is: " + retVal; assert interceptor.assertKeySet; break; } ignore.get(5, TimeUnit.SECONDS); } }); } private enum Visibility { SIZE, GET } public static class EntryCreatedInterceptor extends BaseCustomInterceptor { Log log = LogFactory.getLog(EntryCreatedInterceptor.class); final CountDownLatch latch; volatile boolean assertKeySet; private EntryCreatedInterceptor(CountDownLatch latch) { this.latch = latch; } @Override public Object visitPutKeyValueCommand(InvocationContext ctx, PutKeyValueCommand command) throws Throwable { // First execute the operation itself Object ret = super.visitPutKeyValueCommand(ctx, command); assertKeySet = (cache.keySet().size() == 1); // After entry has been committed to the container log.info("Cache entry created, now check in different thread"); latch.countDown(); // Force a bit of delay in the listener TestingUtil.sleepThread(3000); return ret; } } }