Author: nfilotto
Date: 2011-02-01 12:50:06 -0500 (Tue, 01 Feb 2011)
New Revision: 3910
Modified:
jcr/trunk/exo.jcr.docs/exo.jcr.docs.developer/en/src/main/docbook/en-US/modules/kernel/cache.xml
kernel/trunk/exo.kernel.component.cache/src/main/java/org/exoplatform/services/cache/impl/CacheServiceImpl.java
kernel/trunk/exo.kernel.component.cache/src/test/java/org/exoplatform/services/cache/test/TestCacheService.java
kernel/trunk/exo.kernel.component.cache/src/test/resources/conf/portal/test-configuration.xml
Log:
EXOJCR-1179: Bug fixed
EXOJCR-1180: Improvement implemented to avoid the bug
Modified:
jcr/trunk/exo.jcr.docs/exo.jcr.docs.developer/en/src/main/docbook/en-US/modules/kernel/cache.xml
===================================================================
---
jcr/trunk/exo.jcr.docs/exo.jcr.docs.developer/en/src/main/docbook/en-US/modules/kernel/cache.xml 2011-02-01
14:14:32 UTC (rev 3909)
+++
jcr/trunk/exo.jcr.docs/exo.jcr.docs.developer/en/src/main/docbook/en-US/modules/kernel/cache.xml 2011-02-01
17:50:06 UTC (rev 3910)
@@ -176,7 +176,14 @@
<type>org.exoplatform.tutorial.MyExoCacheFactoryImpl</type>
...
</component>
-</configuration></programlisting></para>
+</configuration></programlisting><note>
+ <para>Since kernel 2.3.0-CR1, if the configuration is not a sub class
+ of <envar>ExoCacheConfig</envar> and the implementation given in the
+ configuration is the full qualified name of an existing implementation
+ of eXo Cache, we will assume that the user expects to have an instance
+ of this eXo Cache type so we won't use the configured cache
+ factory.</para>
+ </note></para>
</section>
<section>
Modified:
kernel/trunk/exo.kernel.component.cache/src/main/java/org/exoplatform/services/cache/impl/CacheServiceImpl.java
===================================================================
---
kernel/trunk/exo.kernel.component.cache/src/main/java/org/exoplatform/services/cache/impl/CacheServiceImpl.java 2011-02-01
14:14:32 UTC (rev 3909)
+++
kernel/trunk/exo.kernel.component.cache/src/main/java/org/exoplatform/services/cache/impl/CacheServiceImpl.java 2011-02-01
17:50:06 UTC (rev 3910)
@@ -28,12 +28,19 @@
import org.exoplatform.services.cache.ExoCacheFactory;
import org.exoplatform.services.cache.ExoCacheInitException;
import org.exoplatform.services.cache.SimpleExoCache;
+import org.exoplatform.services.log.ExoLogger;
+import org.exoplatform.services.log.Log;
import java.io.Serializable;
+import java.util.ArrayList;
import java.util.Collection;
import java.util.HashMap;
import java.util.List;
+import java.util.concurrent.Callable;
+import java.util.concurrent.CancellationException;
import java.util.concurrent.ConcurrentHashMap;
+import java.util.concurrent.ExecutionException;
+import java.util.concurrent.FutureTask;
/**
* Created by The eXo Platform SAS. Author : Tuan Nguyen
@@ -42,11 +49,18 @@
@ManagedBy(CacheServiceManaged.class)
public class CacheServiceImpl implements CacheService
{
+ /**
+ * Logger.
+ */
+ private static final Log LOG =
ExoLogger.getLogger("exo.kernel.component.cache.CacheServiceImpl");
+
+ private final ExoCacheFactory DEFAULT_FACTORY = new SimpleExoCacheFactory();
+
private final HashMap<String, ExoCacheConfig> configs_ = new HashMap<String,
ExoCacheConfig>();
- private final ConcurrentHashMap<String, ExoCache<? extends Serializable,
?>> cacheMap_ =
- new ConcurrentHashMap<String, ExoCache<? extends Serializable, ?>>();
-
+ private final ConcurrentHashMap<String, FutureExoCacheCreationTask> cacheMap_ =
+ new ConcurrentHashMap<String, FutureExoCacheCreationTask>();
+
private final ExoCacheConfig defaultConfig_;
private final LoggingCacheListener loggingListener_;
@@ -72,7 +86,7 @@
}
defaultConfig_ = configs_.get("default");
loggingListener_ = new LoggingCacheListener();
- factory_ = factory == null ? new SimpleExoCacheFactory() : factory;
+ factory_ = factory == null ? DEFAULT_FACTORY : factory;
}
public void addExoCacheConfig(ComponentPlugin plugin)
@@ -89,7 +103,8 @@
}
}
- public <K extends Serializable, V> ExoCache<K, V> getCacheInstance(String
region)
+ @SuppressWarnings("unchecked")
+ public <K extends Serializable, V> ExoCache<K, V> getCacheInstance(final
String region)
{
if (region == null)
{
@@ -99,27 +114,47 @@
{
throw new IllegalArgumentException("region cannot be empty");
}
- ExoCache<? extends Serializable, ?> cache = cacheMap_.get(region);
- if (cache == null)
+ FutureExoCacheCreationTask creationTask = cacheMap_.get(region);
+ if (creationTask == null)
{
- try
+ Callable<ExoCache<? extends Serializable,?>> task = new
Callable<ExoCache<? extends Serializable,?>>()
{
- cache = createCacheInstance(region);
- ExoCache<? extends Serializable, ?> existing =
cacheMap_.putIfAbsent(region, cache);
- if (existing != null)
+ public ExoCache<? extends Serializable, ?> call() throws Exception
{
- cache = existing;
+ return createCacheInstance(region);
}
+ };
+ creationTask = new FutureExoCacheCreationTask(task);
+ FutureExoCacheCreationTask existingTask = cacheMap_.putIfAbsent(region,
creationTask);
+ if (existingTask != null)
+ {
+ creationTask = existingTask;
}
- catch (Exception e)
+ else
{
- e.printStackTrace();
+ creationTask.run();
}
}
- return (ExoCache<K, V>)cache;
+ try
+ {
+ return (ExoCache<K, V>)creationTask.get();
+ }
+ catch (CancellationException e)
+ {
+ cacheMap_.remove(region, creationTask);
+ }
+ catch (InterruptedException e)
+ {
+ Thread.currentThread().interrupt();
+ }
+ catch (ExecutionException e)
+ {
+ LOG.error("Could not create the cache for the region '" + region +
"'", e.getCause());
+ }
+ return null;
}
- synchronized private ExoCache<? extends Serializable, ?>
createCacheInstance(String region) throws Exception
+ private ExoCache<? extends Serializable, ?> createCacheInstance(String region)
throws Exception
{
ExoCacheConfig config = configs_.get(region);
if (config == null)
@@ -129,8 +164,36 @@
final ExoCacheConfig safeConfig = config.clone();
// Set the region as name
safeConfig.setName(region);
- final ExoCache simple = factory_.createCache(safeConfig);
-
+
+ ExoCache simple = null;
+ if (factory_ != DEFAULT_FACTORY &&
safeConfig.getClass().isAssignableFrom(ExoCacheConfig.class)
+ && safeConfig.getImplementation() != null)
+ {
+ // The implementation exists and the config is not a sub class of
ExoCacheConfig
+ // we assume that we expect to use the default cache factory
+ try
+ {
+ final ClassLoader cl = Thread.currentThread().getContextClassLoader();
+ // We check if the given implementation is a known class
+ Class implClass = cl.loadClass(safeConfig.getImplementation());
+ // Implementation is an existing class
+ if (ExoCache.class.isAssignableFrom(implClass))
+ {
+ // The implementation is a sub class of eXo Cache so we use the default
factory
+ simple = DEFAULT_FACTORY.createCache(safeConfig);
+ }
+ }
+ catch (ClassNotFoundException e)
+ {
+ // The implementation could not be found
+ }
+ }
+ if (simple == null)
+ {
+ // We use the configured cache factory
+ simple = factory_.createCache(safeConfig);
+ }
+
if (managed != null)
{
managed.registerCache(simple);
@@ -140,7 +203,24 @@
public Collection<ExoCache<? extends Serializable, ?>>
getAllCacheInstances()
{
- return cacheMap_.values();
+ Collection<ExoCache<? extends Serializable, ?>> caches = new
ArrayList<ExoCache<? extends Serializable,?>>(cacheMap_.size());
+ for (FutureTask<ExoCache<? extends Serializable,?>> task :
cacheMap_.values())
+ {
+ ExoCache<? extends Serializable, ?> cache = null;
+ try
+ {
+ cache = task.get();
+ }
+ catch (Exception e)
+ {
+ // ignore me
+ }
+ if (cache != null)
+ {
+ caches.add(cache);
+ }
+ }
+ return caches;
}
/**
@@ -203,4 +283,31 @@
}
}
}
+
+ /**
+ * This class is used to reduce the contention when the cache is already created
+ */
+ private static class FutureExoCacheCreationTask extends FutureTask<ExoCache<?
extends Serializable, ?>>
+ {
+
+ private volatile ExoCache<? extends Serializable, ?> cache;
+
+ /**
+ * @param callable
+ */
+ public FutureExoCacheCreationTask(Callable<ExoCache<? extends Serializable,
?>> callable)
+ {
+ super(callable);
+ }
+
+ @Override
+ public ExoCache<? extends Serializable, ?> get() throws InterruptedException,
ExecutionException
+ {
+ if (cache != null)
+ {
+ return cache;
+ }
+ return cache = super.get();
+ }
+ }
}
Modified:
kernel/trunk/exo.kernel.component.cache/src/test/java/org/exoplatform/services/cache/test/TestCacheService.java
===================================================================
---
kernel/trunk/exo.kernel.component.cache/src/test/java/org/exoplatform/services/cache/test/TestCacheService.java 2011-02-01
14:14:32 UTC (rev 3909)
+++
kernel/trunk/exo.kernel.component.cache/src/test/java/org/exoplatform/services/cache/test/TestCacheService.java 2011-02-01
17:50:06 UTC (rev 3910)
@@ -19,17 +19,31 @@
package org.exoplatform.services.cache.test;
import org.exoplatform.container.PortalContainer;
+import org.exoplatform.container.xml.InitParams;
+import org.exoplatform.container.xml.ObjectParameter;
+import org.exoplatform.services.cache.CacheListener;
import org.exoplatform.services.cache.CacheService;
+import org.exoplatform.services.cache.CachedObjectSelector;
import org.exoplatform.services.cache.ExoCache;
+import org.exoplatform.services.cache.ExoCacheConfig;
+import org.exoplatform.services.cache.ExoCacheFactory;
+import org.exoplatform.services.cache.ExoCacheInitException;
import org.exoplatform.services.cache.FIFOExoCache;
import org.exoplatform.services.cache.SimpleExoCache;
+import org.exoplatform.services.cache.concurrent.ConcurrentFIFOExoCache;
+import org.exoplatform.services.cache.impl.CacheServiceImpl;
import org.exoplatform.test.BasicTestCase;
import java.io.Serializable;
+import java.util.ArrayList;
import java.util.Collection;
+import java.util.Collections;
import java.util.Comparator;
import java.util.HashMap;
+import java.util.List;
import java.util.Map;
+import java.util.concurrent.CountDownLatch;
+import java.util.concurrent.atomic.AtomicInteger;
import javax.management.MBeanAttributeInfo;
import javax.management.MBeanInfo;
@@ -194,6 +208,179 @@
}
*/
+ public void testConcurrentCreation() throws Exception
+ {
+ int threads = 20;
+ final CountDownLatch startSignal = new CountDownLatch(1);
+ final CountDownLatch doneSignal = new CountDownLatch(threads);
+ final List<Exception> errors = Collections.synchronizedList(new
ArrayList<Exception>());
+ for (int i = 0; i < threads; i++)
+ {
+ Thread thread = new Thread()
+ {
+ public void run()
+ {
+ try
+ {
+ startSignal.await();
+
assertNotNull(service_.getCacheInstance("TestConcurrentCreation"));
+ }
+ catch (Exception e)
+ {
+ errors.add(e);
+ }
+ finally
+ {
+ doneSignal.countDown();
+ }
+ }
+ };
+ thread.start();
+ }
+ startSignal.countDown();
+ doneSignal.await();
+ if (!errors.isEmpty())
+ {
+ for (Exception e : errors)
+ {
+ e.printStackTrace();
+ }
+ throw errors.get(0);
+ }
+ assertEquals(1, MyExoCache.count.get());
+ }
+
+ public void testGetAllCacheInstances() throws Exception
+ {
+ assertNotNull(service_.getAllCacheInstances());
+ assertEquals(8, service_.getAllCacheInstances().size());
+ }
+
+ public void testPerf() throws Exception
+ {
+ // Pre-create it
+ service_.getCacheInstance("FooCache");
+ int threads = 100;
+ final CountDownLatch startSignal = new CountDownLatch(1);
+ final CountDownLatch doneSignal = new CountDownLatch(threads);
+ final List<Exception> errors = Collections.synchronizedList(new
ArrayList<Exception>());
+ for (int i = 0; i < threads; i++)
+ {
+ Thread thread = new Thread()
+ {
+ public void run()
+ {
+ try
+ {
+ startSignal.await();
+ for (int i = 0; i < 1000000; i++)
+ {
+ assertNotNull(service_.getCacheInstance("FooCache"));
+ }
+ }
+ catch (Exception e)
+ {
+ errors.add(e);
+ }
+ finally
+ {
+ doneSignal.countDown();
+ }
+ }
+ };
+ thread.start();
+ }
+ long start = System.currentTimeMillis();
+ startSignal.countDown();
+ doneSignal.await();
+ System.out.println("Total Time = " + (System.currentTimeMillis() -
start));
+ if (!errors.isEmpty())
+ {
+ for (Exception e : errors)
+ {
+ e.printStackTrace();
+ }
+ throw errors.get(0);
+ }
+ }
+
+ public void testCacheFactory() throws Exception
+ {
+ InitParams params = new InitParams();
+ ObjectParameter param = new ObjectParameter();
+ param.setName("NoImpl");
+ ExoCacheConfig config = new ExoCacheConfig();
+ config.setName(param.getName());
+ param.setObject(config);
+ params.addParameter(param);
+
+ param = new ObjectParameter();
+ param.setName("KnownImpl");
+ config = new ExoCacheConfig();
+ config.setName(param.getName());
+
config.setImplementation("org.exoplatform.services.cache.concurrent.ConcurrentFIFOExoCache");
+ param.setObject(config);
+ params.addParameter(param);
+
+ param = new ObjectParameter();
+ param.setName("UnKnownImpl");
+ config = new ExoCacheConfig();
+ config.setName(param.getName());
+ config.setImplementation("fakeImpl");
+ param.setObject(config);
+ params.addParameter(param);
+
+ param = new ObjectParameter();
+ param.setName("UnKnownImplButCorrectFQN");
+ config = new ExoCacheConfig();
+ config.setName(param.getName());
+ config.setImplementation("java.lang.String");
+ param.setObject(config);
+ params.addParameter(param);
+
+ param = new ObjectParameter();
+ param.setName("NoImpl-MyExoCacheConfig");
+ config = new MyExoCacheConfig();
+ config.setName(param.getName());
+ param.setObject(config);
+ params.addParameter(param);
+
+ param = new ObjectParameter();
+ param.setName("KnownImpl-MyExoCacheConfig");
+ config = new MyExoCacheConfig();
+ config.setName(param.getName());
+ config.setImplementation("org.exoplatform.services.cache.FIFOExoCache");
+ param.setObject(config);
+ params.addParameter(param);
+
+ param = new ObjectParameter();
+ param.setName("UnKnownImpl-MyExoCacheConfig");
+ config = new MyExoCacheConfig();
+ config.setName(param.getName());
+ config.setImplementation("fakeImpl");
+ param.setObject(config);
+ params.addParameter(param);
+
+
+ param = new ObjectParameter();
+ param.setName("UnKnownImplButCorrectFQN-MyExoCacheConfig");
+ config = new MyExoCacheConfig();
+ config.setName(param.getName());
+ config.setImplementation("java.lang.String");
+ param.setObject(config);
+ params.addParameter(param);
+
+ CacheService cs = new CacheServiceImpl(params, new MyExoCacheFactory());
+ assertTrue("Expected type MyExoCache found " +
cs.getCacheInstance("NoImpl").getClass(),
cs.getCacheInstance("NoImpl") instanceof MyExoCache);
+ assertTrue("Expected type ConcurrentFIFOExoCache found " +
cs.getCacheInstance("KnownImpl").getClass(),
cs.getCacheInstance("KnownImpl") instanceof ConcurrentFIFOExoCache);
+ assertTrue("Expected type MyExoCache found " +
cs.getCacheInstance("UnKnownImpl").getClass(),
cs.getCacheInstance("UnKnownImpl") instanceof MyExoCache);
+ assertTrue("Expected type MyExoCache found " +
cs.getCacheInstance("UnKnownImplButCorrectFQN").getClass(),
cs.getCacheInstance("UnKnownImplButCorrectFQN") instanceof MyExoCache);
+ assertTrue("Expected type MyExoCache found " +
cs.getCacheInstance("NoImpl-MyExoCacheConfig").getClass(),
cs.getCacheInstance("NoImpl-MyExoCacheConfig") instanceof MyExoCache);
+ assertTrue("Expected type MyExoCache found " +
cs.getCacheInstance("KnownImpl-MyExoCacheConfig").getClass(),
cs.getCacheInstance("KnownImpl-MyExoCacheConfig") instanceof MyExoCache);
+ assertTrue("Expected type MyExoCache found " +
cs.getCacheInstance("UnKnownImpl-MyExoCacheConfig").getClass(),
cs.getCacheInstance("UnKnownImpl-MyExoCacheConfig") instanceof MyExoCache);
+ assertTrue("Expected type MyExoCache found " +
cs.getCacheInstance("UnKnownImplButCorrectFQN-MyExoCacheConfig").getClass(),
cs.getCacheInstance("UnKnownImplButCorrectFQN-MyExoCacheConfig") instanceof
MyExoCache);
+ }
+
private static class ExoCacheComparator implements Comparator
{
@@ -214,4 +401,202 @@
{
return "Test Cache Service";
}
+
+ public static class MyExoCache<V> implements ExoCache<Serializable, V>
+ {
+
+ private static AtomicInteger count = new AtomicInteger();
+
+ public MyExoCache()
+ {
+ count.incrementAndGet();
+ }
+
+ /**
+ * @see org.exoplatform.services.cache.ExoCache#getName()
+ */
+ public String getName()
+ {
+ return "TestConcurrentCreation";
+ }
+
+ /**
+ * @see org.exoplatform.services.cache.ExoCache#setName(java.lang.String)
+ */
+ public void setName(String name)
+ {
+ }
+
+ /**
+ * @see org.exoplatform.services.cache.ExoCache#getLabel()
+ */
+ public String getLabel()
+ {
+ return "TestConcurrentCreation";
+ }
+
+ /**
+ * @see org.exoplatform.services.cache.ExoCache#setLabel(java.lang.String)
+ */
+ public void setLabel(String s)
+ {
+ }
+
+ /**
+ * @see org.exoplatform.services.cache.ExoCache#get(java.io.Serializable)
+ */
+ public V get(Serializable key)
+ {
+ return null;
+ }
+
+ /**
+ * @see org.exoplatform.services.cache.ExoCache#remove(java.io.Serializable)
+ */
+ public V remove(Serializable key) throws NullPointerException
+ {
+ return null;
+ }
+
+ /**
+ * @see org.exoplatform.services.cache.ExoCache#put(java.io.Serializable,
java.lang.Object)
+ */
+ public void put(Serializable key, V value) throws NullPointerException
+ {
+
+ }
+
+ /**
+ * @see org.exoplatform.services.cache.ExoCache#putMap(java.util.Map)
+ */
+ public void putMap(Map<? extends Serializable, ? extends V> objs) throws
NullPointerException,
+ IllegalArgumentException
+ {
+
+ }
+
+ /**
+ * @see org.exoplatform.services.cache.ExoCache#clearCache()
+ */
+ public void clearCache()
+ {
+
+ }
+
+ /**
+ * @see
org.exoplatform.services.cache.ExoCache#select(org.exoplatform.services.cache.CachedObjectSelector)
+ */
+ public void select(CachedObjectSelector<? super Serializable, ? super V>
selector) throws Exception
+ {
+
+ }
+
+ /**
+ * @see org.exoplatform.services.cache.ExoCache#getCacheSize()
+ */
+ public int getCacheSize()
+ {
+ return 0;
+ }
+
+ /**
+ * @see org.exoplatform.services.cache.ExoCache#getMaxSize()
+ */
+ public int getMaxSize()
+ {
+ return 0;
+ }
+
+ /**
+ * @see org.exoplatform.services.cache.ExoCache#setMaxSize(int)
+ */
+ public void setMaxSize(int max)
+ {
+ }
+
+ /**
+ * @see org.exoplatform.services.cache.ExoCache#getLiveTime()
+ */
+ public long getLiveTime()
+ {
+ return 0;
+ }
+
+ /**
+ * @see org.exoplatform.services.cache.ExoCache#setLiveTime(long)
+ */
+ public void setLiveTime(long period)
+ {
+ }
+
+ /**
+ * @see org.exoplatform.services.cache.ExoCache#getCacheHit()
+ */
+ public int getCacheHit()
+ {
+ return 0;
+ }
+
+ /**
+ * @see org.exoplatform.services.cache.ExoCache#getCacheMiss()
+ */
+ public int getCacheMiss()
+ {
+ return 0;
+ }
+
+ /**
+ * @see org.exoplatform.services.cache.ExoCache#getCachedObjects()
+ */
+ public List<? extends V> getCachedObjects() throws Exception
+ {
+ return null;
+ }
+
+ /**
+ * @see org.exoplatform.services.cache.ExoCache#removeCachedObjects()
+ */
+ public List<? extends V> removeCachedObjects()
+ {
+ return null;
+ }
+
+ /**
+ * @see
org.exoplatform.services.cache.ExoCache#addCacheListener(org.exoplatform.services.cache.CacheListener)
+ */
+ public void addCacheListener(CacheListener<? super Serializable, ? super V>
listener) throws NullPointerException
+ {
+ }
+
+ /**
+ * @see org.exoplatform.services.cache.ExoCache#isLogEnabled()
+ */
+ public boolean isLogEnabled()
+ {
+ return false;
+ }
+
+ /**
+ * @see org.exoplatform.services.cache.ExoCache#setLogEnabled(boolean)
+ */
+ public void setLogEnabled(boolean b)
+ {
+ }
+
+ }
+
+ public static class MyExoCacheFactory implements ExoCacheFactory
+ {
+
+ /**
+ * @see
org.exoplatform.services.cache.ExoCacheFactory#createCache(org.exoplatform.services.cache.ExoCacheConfig)
+ */
+ public ExoCache createCache(ExoCacheConfig config) throws ExoCacheInitException
+ {
+ return new MyExoCache();
+ }
+
+ }
+
+ public static class MyExoCacheConfig extends ExoCacheConfig {}
}
Modified:
kernel/trunk/exo.kernel.component.cache/src/test/resources/conf/portal/test-configuration.xml
===================================================================
---
kernel/trunk/exo.kernel.component.cache/src/test/resources/conf/portal/test-configuration.xml 2011-02-01
14:14:32 UTC (rev 3909)
+++
kernel/trunk/exo.kernel.component.cache/src/test/resources/conf/portal/test-configuration.xml 2011-02-01
17:50:06 UTC (rev 3910)
@@ -223,6 +223,31 @@
</object>
</object-param>
+ <object-param>
+ <name>TestConcurrentCreationPlugin</name>
+ <description>cache used to test the concurrent
creation</description>
+ <object
type="org.exoplatform.services.cache.ExoCacheConfig">
+ <field name="name">
+ <string>TestConcurrentCreation</string>
+ </field>
+ <field name="implementation">
+
<string>org.exoplatform.services.cache.test.TestCacheService$MyExoCache</string>
+ </field>
+ </object>
+ </object-param>
+
+ <object-param>
+ <name>TestErrorCreationPlugin</name>
+ <description>cache used to test the concurrent
creation</description>
+ <object
type="org.exoplatform.services.cache.ExoCacheConfig">
+ <field name="name">
+ <string>TestErrorCreation</string>
+ </field>
+ <field name="implementation">
+
<string>org.exoplatform.services.cache.test.TestCacheService$MyExoCacheFake</string>
+ </field>
+ </object>
+ </object-param>
</init-params>
</component-plugin>
</external-component-plugins>