package org.jboss.cache.loader;
import org.jboss.cache.CacheException;
import org.jboss.cache.CacheSPI;
import org.jboss.cache.DefaultCacheFactory;
import org.jboss.cache.Fqn;
import org.jboss.cache.Modification;
import org.jboss.cache.config.CacheLoaderConfig;
import org.jboss.cache.config.CacheLoaderConfig.IndividualCacheLoaderConfig;
import static org.testng.AssertJUnit.assertTrue;
import org.testng.annotations.Test;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.List;
import java.util.Map;
import java.util.Set;
/**
* Test case that proves that the CacheImpl is serialized inside the CacheLoaderInterceptor
* when the node is empty. ANY call to retrieve a node that is not loaded will lock
* up inside CacheLoaderInterceptor while the node is loaded and any other thread
* that wants a node out of the cache will wait for the first one to finish before it can even _start_ loading.
*
* @author paulsmith
*/
@Test(groups = {"functional"})
public class InterceptorSynchronizationTest
{
final int CACHELOADER_WAITTIME = 2000;// lets say loading a node takes 2 seconds
final int numThreadsPerTopLevelNode = 5;// we'll have 5 requests for nodes within a branch
@SuppressWarnings("deprecation")
public void testBlockingProblem() throws Exception
{
CacheSPI<Object, Object> cache = (CacheSPI<Object, Object>) new DefaultCacheFactory().createCache(false);
//setCacheLoader(new TestSlowCacheLoader());
CacheLoaderConfig clc = new CacheLoaderConfig();
IndividualCacheLoaderConfig iclc = new IndividualCacheLoaderConfig();
//iclc.setClassName(TestSlowCacheLoader.class.getName());
iclc.setCacheLoader(new TestSlowCacheLoader());
clc.addIndividualCacheLoaderConfig(iclc);
cache.getConfiguration().setCacheLoaderConfig(clc);
cache.start();
long begin = System.currentTimeMillis();
Collection<Thread> threads = new ArrayList<Thread>();
/*
* Create lots of threads all trying to load DIFFERENT fqn's, as well as a set with different top level nodes.
*/
for (int i = 0; i < numThreadsPerTopLevelNode; i++)
{
Thread thread = new Thread(new Retriever(cache, "/Moo/" + i));
threads.add(thread);
Thread thread2 = new Thread(new Retriever(cache, "/Meow/" + i));
threads.add(thread2);
}
for (Thread thread : threads)
{
thread.start();
}
for (Thread thread : threads)
{
thread.join();
}
long end = System.currentTimeMillis();
long timeTaken = (end - begin);
/*
* My expectation is that if there are 2 top level nodes they should be loaded in parallel at the very least,
* but even bottom level nodes that are different should be able to be loaded concurrently.
*
* In this test, NONE of the threads operate in parallel once entered into CacheLoaderInterceptor.
*/
int totalTimeExpectedToWaitIfNotSerialized = 3 * CACHELOADER_WAITTIME;// i'm being very generous here. 3 times the wait time for each node is more than enough if it was in parallel.
assertTrue("If it was parallel, it should have finished quicker than this:" + timeTaken, timeTaken < totalTimeExpectedToWaitIfNotSerialized);
}
/**
* Dummy cache loader that emulates a slow loading of any node from a virtual backing store.
*
* @author paulsmith
*/
public class TestSlowCacheLoader extends AbstractCacheLoader
{
public void setConfig(IndividualCacheLoaderConfig config)
{
}
public IndividualCacheLoaderConfig getConfig()
{
return null;
}
public Set<?> getChildrenNames(Fqn arg0) throws Exception
{
return null;
}
public Object get(Fqn arg0, Object arg1)
{
return null;
}
public Map<Object, Object> get(Fqn arg0) throws Exception
{
Thread.sleep(CACHELOADER_WAITTIME);
return Collections.singletonMap((Object) "foo", (Object) "bar");
}
public boolean exists(Fqn arg0) throws Exception
{
return true;
}
public Object put(Fqn arg0, Object arg1, Object arg2) throws Exception
{
return null;
}
public void put(Fqn arg0, Map arg1) throws Exception
{
}
public void put(List<Modification> modifications) throws Exception
{
}
public Object remove(Fqn arg0, Object arg1) throws Exception
{
return null;
}
public void remove(Fqn arg0) throws Exception
{
}
public void removeData(Fqn arg0) throws Exception
{
}
public void prepare(Object tx, List<Modification> modifications, boolean one_phase) throws Exception
{
}
public void commit(Object arg0) throws Exception
{
}
public void rollback(Object arg0)
{
}
public void loadEntireState(ObjectOutputStream os) throws Exception
{
// nothing to do here
}
public void loadState(Fqn subtree, ObjectOutputStream os) throws Exception
{
// nothing to do here
}
public void storeEntireState(ObjectInputStream is) throws Exception
{
// nothing to do here
}
public void storeState(Fqn subtree, ObjectInputStream is) throws Exception
{
// nothing to do here
}
public void create() throws Exception
{
}
public void start() throws Exception
{
}
public void stop()
{
}
public void destroy()
{
}
}
private static class Retriever implements Runnable
{
private final String fqn;
private CacheSPI<Object, Object> cache;
private Retriever(CacheSPI<Object, Object> cache, String fqn)
{
this.fqn = fqn;
this.cache = cache;
}
public void run()
{
try
{
cache.get(fqn, "foo");
}
catch (CacheException e)
{
throw new RuntimeException("Unexpected", e);
}
}
}
}