/*
* JBoss, Home of Professional Open Source
* Copyright 2009 Red Hat Inc. and/or its affiliates and other
* contributors as indicated by the @author tags. All rights reserved.
* See the copyright.txt in the distribution for a full listing of
* individual contributors.
*
* This is free software; you can redistribute it and/or modify it
* under the terms of the GNU Lesser General Public License as
* published by the Free Software Foundation; either version 2.1 of
* the License, or (at your option) any later version.
*
* This software is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public
* License along with this software; if not, write to the Free
* Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA
* 02110-1301 USA, or see the FSF site: http://www.fsf.org.
*/
package org.infinispan.loaders;
import org.infinispan.commands.read.GetKeyValueCommand;
import org.infinispan.commands.write.EvictCommand;
import org.infinispan.config.CloneableConfigurationComponent;
import org.infinispan.config.Configuration;
import org.infinispan.container.DataContainer;
import org.infinispan.container.entries.InternalCacheEntry;
import org.infinispan.context.InvocationContext;
import org.infinispan.interceptors.InvocationContextInterceptor;
import org.infinispan.interceptors.base.CommandInterceptor;
import org.infinispan.loaders.dummy.DummyInMemoryCacheStore;
import org.infinispan.manager.EmbeddedCacheManager;
import org.infinispan.test.SingleCacheManagerTest;
import org.infinispan.test.TestingUtil;
import org.infinispan.test.fwk.TestCacheManagerFactory;
import org.infinispan.transaction.TransactionMode;
import org.testng.annotations.Test;
import java.util.concurrent.Callable;
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.Future;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.TimeoutException;
import static org.infinispan.context.Flag.SKIP_CACHE_STORE;
/**
* Tests a thread going past the cache loader interceptor and the interceptor deciding that loading is not necessary,
* then another thread rushing ahead and evicting the entry from memory.
*
* @author Manik Surtani
*/
@Test(groups = "functional", testName = "loaders.ConcurrentLoadAndEvictTest")
public class ConcurrentLoadAndEvictTest extends SingleCacheManagerTest {
SlowDownInterceptor sdi;
protected EmbeddedCacheManager createCacheManager() throws Exception {
sdi = new SlowDownInterceptor();
// we need a loader and a custom interceptor to intercept get() calls
// after the CLI, to slow it down so an evict goes through first
Configuration config = new Configuration().fluent()
.loaders()
.addCacheLoader(new DummyInMemoryCacheStore.Cfg())
.customInterceptors()
.add(sdi).after(InvocationContextInterceptor.class)
.transaction().transactionMode(TransactionMode.NON_TRANSACTIONAL)
.build();
return TestCacheManagerFactory.createCacheManager(config);
}
public void testEvictBeforeRead() throws CacheLoaderException, ExecutionException, InterruptedException {
cache = cacheManager.getCache();
cache.put("a", "b");
assert cache.get("a").equals("b");
CacheLoader cl = TestingUtil.getCacheLoader(cache);
assert cl != null;
InternalCacheEntry se = cl.load("a");
assert se != null;
assert se.getValue().equals("b");
// clear the cache
cache.getAdvancedCache().withFlags(SKIP_CACHE_STORE).clear();
se = cl.load("a");
assert se != null;
assert se.getValue().equals("b");
// now attempt a concurrent get and evict.
ExecutorService e = Executors.newFixedThreadPool(1);
sdi.enabled = true;
log.info("test::doing the get");
// call the get
Future<String> future = e.submit(new Callable<String>() {
public String call() throws Exception {
return (String) cache.get("a");
}
});
// now run the evict.
log.info("test::before the evict");
cache.evict("a");
log.info("test::after the evict");
// make sure the get call, which would have gone past the cache loader interceptor first, gets the correct value.
assert future.get().equals("b");
// disable the SlowDownInterceptor
sdi.enabled = false;
// and check that the key actually has been evicted
assert !TestingUtil.extractComponent(cache, DataContainer.class).containsKey("a", null);
e.shutdownNow();
}
public static class SlowDownInterceptor extends CommandInterceptor implements CloneableConfigurationComponent{
private static final long serialVersionUID = 8790944676490291484L;
volatile boolean enabled = false;
transient CountDownLatch getLatch = new CountDownLatch(1);
transient CountDownLatch evictLatch = new CountDownLatch(1);
@Override
public Object visitGetKeyValueCommand(InvocationContext ctx, GetKeyValueCommand command) throws Throwable {
if (enabled) {
getLog().trace("Wait for evict to give go ahead...");
if (!evictLatch.await(60000, TimeUnit.MILLISECONDS))
throw new TimeoutException("Didn't see get after 60 seconds!");
}
try {
return invokeNextInterceptor(ctx, command);
} finally {
getLog().trace("After get, now let evict go through");
if (enabled) getLatch.countDown();
}
}
@Override
public Object visitEvictCommand(InvocationContext ctx, EvictCommand command) throws Throwable {
if (enabled) {
evictLatch.countDown();
getLog().trace("Wait for get to finish...");
if (!getLatch.await(60000, TimeUnit.MILLISECONDS))
throw new TimeoutException("Didn't see evict after 60 seconds!");
}
return invokeNextInterceptor(ctx, command);
}
public SlowDownInterceptor clone(){
try {
return (SlowDownInterceptor) super.clone();
} catch (CloneNotSupportedException e) {
throw new RuntimeException("Should not happen", e);
}
}
}
}