diff --git a/config/alfresco/ehcache-default.xml b/config/alfresco/ehcache-default.xml index b26824bc19..8d8bbb4f38 100644 --- a/config/alfresco/ehcache-default.xml +++ b/config/alfresco/ehcache-default.xml @@ -11,6 +11,7 @@ timeToIdleSeconds="0" timeToLiveSeconds="0" overflowToDisk="false" + statistics="false" > @@ -20,6 +21,7 @@ maxElementsInMemory="50" eternal="true" overflowToDisk="false" + statistics="false" /> @@ -77,6 +86,7 @@ maxElementsInMemory="1000" eternal="true" overflowToDisk="false" + statistics="false" /> @@ -179,6 +203,7 @@ maxElementsInMemory="5000" eternal="true" overflowToDisk="false" + statistics="false" /> @@ -216,102 +245,119 @@ eternal="false" timeToLiveSeconds="300" overflowToDisk="false" + statistics="false" /> @@ -321,6 +367,7 @@ maxElementsInMemory="100" eternal="true" overflowToDisk="false" + statistics="false" /> @@ -330,6 +377,7 @@ maxElementsInMemory="100" eternal="true" overflowToDisk="false" + statistics="false" /> @@ -354,6 +404,7 @@ maxElementsInMemory="10000" eternal="true" overflowToDisk="false" + statistics="false" /> @@ -362,6 +413,7 @@ maxElementsInMemory="10000" eternal="true" overflowToDisk="false" + statistics="false" /> @@ -371,6 +423,7 @@ maxElementsInMemory="100" eternal="true" overflowToDisk="false" + statistics="false" /> @@ -380,6 +433,7 @@ maxElementsInMemory="10000" eternal="true" overflowToDisk="false" + statistics="false" /> @@ -389,6 +443,7 @@ maxElementsInMemory="100" eternal="true" overflowToDisk="false" + statistics="false" /> @@ -398,6 +453,7 @@ maxElementsInMemory="10000" eternal="true" overflowToDisk="false" + statistics="false" /> \ No newline at end of file diff --git a/config/alfresco/extension/ehcache-custom.xml.sample.cluster b/config/alfresco/extension/ehcache-custom.xml.sample.cluster index 47c01ddba4..61ef2eeb9d 100644 --- a/config/alfresco/extension/ehcache-custom.xml.sample.cluster +++ b/config/alfresco/extension/ehcache-custom.xml.sample.cluster @@ -22,7 +22,9 @@ eternal="true" timeToIdleSeconds="0" timeToLiveSeconds="0" - overflowToDisk="false"> + overflowToDisk="false" + statistics="false" + > + overflowToDisk="false" + statistics="false" + > + overflowToDisk="false" + statistics="false" + > + overflowToDisk="false" + statistics="false" + > + overflowToDisk="false" + statistics="false" + > + overflowToDisk="false" + statistics="false" + > + overflowToDisk="false" + statistics="false" + > + overflowToDisk="false" + statistics="false" + > + overflowToDisk="false" + statistics="false" + > + overflowToDisk="false" + statistics="false" + > + overflowToDisk="false" + statistics="false" + > + overflowToDisk="false" + statistics="false" + > + overflowToDisk="false" + statistics="false" + > + overflowToDisk="false" + statistics="false" + > + overflowToDisk="false" + statistics="false" + > + overflowToDisk="false" + statistics="false" + > + overflowToDisk="false" + statistics="false" + > + overflowToDisk="false" + statistics="false" + > + overflowToDisk="false" + statistics="false" + > + overflowToDisk="false" + statistics="false" + > + overflowToDisk="false" + statistics="false" + > + overflowToDisk="false" + statistics="false" + > + overflowToDisk="false" + statistics="false" + > + overflowToDisk="false" + statistics="false" + > + overflowToDisk="false" + statistics="false" + > + overflowToDisk="false" + statistics="false" + > + overflowToDisk="false" + statistics="false" + > + overflowToDisk="false" + statistics="false" + > + overflowToDisk="false" + statistics="false" + > + overflowToDisk="false" + statistics="false" + > + overflowToDisk="false" + statistics="false" + > + overflowToDisk="false" + statistics="false" + > + overflowToDisk="false" + statistics="false" + > + overflowToDisk="false" + statistics="false" + > + overflowToDisk="false" + statistics="false" + > + overflowToDisk="false" + statistics="false" + > + overflowToDisk="false" + statistics="false" + > + overflowToDisk="false" + statistics="false" + > + overflowToDisk="false" + statistics="false" + > + overflowToDisk="false" + statistics="false" + > + overflowToDisk="false" + statistics="false" + > + overflowToDisk="false" + statistics="false" + > + overflowToDisk="false" + statistics="false" + > + overflowToDisk="false" + statistics="false" + > + overflowToDisk="true" + statistics="false" + > + overflowToDisk="false" + statistics="false" + > + overflowToDisk="false" + statistics="false" + > + overflowToDisk="false" + statistics="false" + > + overflowToDisk="false" + statistics="false" + > + overflowToDisk="false" + statistics="false" + > + overflowToDisk="false" + statistics="false" + > + overflowToDisk="false" + statistics="false" + > + overflowToDisk="false" + statistics="false" + > + overflowToDisk="false" + statistics="false" + > + overflowToDisk="false" + statistics="false" + > + overflowToDisk="false" + statistics="false" + > standaloneCache; @@ -77,9 +77,9 @@ public class CacheTest extends TestCase public void testSetUp() throws Exception { - CacheManager cacheManager = (CacheManager) ctx.getBean("ehCacheManager"); + CacheManager cacheManager = (CacheManager) ctx.getBean("testEHCacheManager"); assertNotNull(cacheManager); - CacheManager cacheManagerCheck = (CacheManager) ctx.getBean("ehCacheManager"); + CacheManager cacheManagerCheck = (CacheManager) ctx.getBean("testEHCacheManager"); assertTrue(cacheManager == cacheManagerCheck); assertNotNull(serviceRegistry); @@ -344,7 +344,7 @@ public class CacheTest extends TestCase */ public void testPerformance() throws Exception { - for (int i = 0; i < 5; i++) + for (int i = 0; i < 6; i++) { int count = (int) Math.pow(10D, (double)i); @@ -371,6 +371,32 @@ public class CacheTest extends TestCase } } + /** + * @see #testPerformance() + */ + public static void main(String ... args) + { + try + { + CacheTest test = new CacheTest(); + test.setUp(); + System.out.println("Press any key to run test ..."); + System.in.read(); + test.testPerformance(); + System.out.println("Press any key to shutdown ..."); + System.in.read(); + test.tearDown(); + } + catch (Throwable e) + { + e.printStackTrace(); + } + finally + { + ApplicationContextHelper.closeApplicationContext(); + } + } + /** * Starts off with a null in the backing cache and adds a value to the * transactional cache. There should be no problem with this. @@ -412,7 +438,7 @@ public class CacheTest extends TestCase assertEquals("The start value isn't correct", startValue, transactionalCache.get(startKey)); - for (int i = 0; i < 50000; i++) + for (int i = 0; i < 205000; i++) { Object value = Integer.valueOf(i); String key = value.toString(); diff --git a/source/java/org/alfresco/repo/cache/EhCacheManagerFactoryBean.java b/source/java/org/alfresco/repo/cache/EhCacheManagerFactoryBean.java index 43009ffe49..df29312b98 100644 --- a/source/java/org/alfresco/repo/cache/EhCacheManagerFactoryBean.java +++ b/source/java/org/alfresco/repo/cache/EhCacheManagerFactoryBean.java @@ -43,6 +43,13 @@ import org.springframework.core.io.Resource; */ public class EhCacheManagerFactoryBean implements FactoryBean, InitializingBean, DisposableBean { + static + { + // https://jira.terracotta.org/jira/browse/EHC-652 + // Force old-style LruMemoryStore + System.setProperty("net.sf.ehcache.use.classic.lru", "true"); + } + protected final Log logger = LogFactory.getLog(EhCacheManagerFactoryBean.class); private Resource configLocation; diff --git a/source/java/org/alfresco/repo/cache/InternalEhCacheManagerFactoryBean.java b/source/java/org/alfresco/repo/cache/InternalEhCacheManagerFactoryBean.java index 08ce8b124d..6ccab7c1bb 100644 --- a/source/java/org/alfresco/repo/cache/InternalEhCacheManagerFactoryBean.java +++ b/source/java/org/alfresco/repo/cache/InternalEhCacheManagerFactoryBean.java @@ -63,6 +63,13 @@ import org.springframework.util.ResourceUtils; */ public class InternalEhCacheManagerFactoryBean implements FactoryBean, CacheProvider { + static + { + // https://jira.terracotta.org/jira/browse/EHC-652 + // Force old-style LruMemoryStore + System.setProperty("net.sf.ehcache.use.classic.lru", "true"); + } + public static final String CUSTOM_CONFIGURATION_FILE = "classpath:alfresco/extension/ehcache-custom.xml"; public static final String DEFAULT_CONFIGURATION_FILE = "classpath:alfresco/ehcache-default.xml"; diff --git a/source/java/org/alfresco/repo/cache/TransactionalCache.java b/source/java/org/alfresco/repo/cache/TransactionalCache.java index b76af35b4c..e58a1ba0a7 100644 --- a/source/java/org/alfresco/repo/cache/TransactionalCache.java +++ b/source/java/org/alfresco/repo/cache/TransactionalCache.java @@ -21,17 +21,17 @@ package org.alfresco.repo.cache; import java.io.Serializable; import java.util.Collection; import java.util.HashSet; -import java.util.List; +import java.util.Map; +import java.util.Set; -import net.sf.ehcache.Cache; import net.sf.ehcache.CacheException; import net.sf.ehcache.CacheManager; -import net.sf.ehcache.Element; import org.alfresco.error.AlfrescoRuntimeException; import org.alfresco.repo.transaction.AlfrescoTransactionSupport; import org.alfresco.repo.transaction.TransactionListener; import org.alfresco.util.EqualsHelper; +import org.apache.commons.collections.map.LRUMap; import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; import org.springframework.beans.factory.InitializingBean; @@ -83,9 +83,6 @@ public class TransactionalCache /** the shared cache that will get updated after commits */ private SimpleCache sharedCache; - /** the manager to control Ehcache caches */ - private CacheManager cacheManager; - /** the maximum number of elements to be contained in the cache */ private int maxCacheSize = 500; @@ -145,13 +142,10 @@ public class TransactionalCache } /** - * Set the manager to activate and control the cache instances - * - * @param cacheManager + * No-op */ public void setCacheManager(CacheManager cacheManager) { - this.cacheManager = cacheManager; } /** @@ -185,7 +179,6 @@ public class TransactionalCache public void afterPropertiesSet() throws Exception { Assert.notNull(name, "name property not set"); - Assert.notNull(cacheManager, "cacheManager property not set"); // generate the resource binding key resourceKeyTxnData = RESOURCE_KEY_TXN_DATA + "." + name; // Refine the log category @@ -201,30 +194,14 @@ public class TransactionalCache TransactionData data = (TransactionData) AlfrescoTransactionSupport.getResource(resourceKeyTxnData); if (data == null) { - String txnId = AlfrescoTransactionSupport.getTransactionId(); data = new TransactionData(); // create and initialize caches - data.updatedItemsCache = new Cache( - name + "_"+ txnId + "_updates", - maxCacheSize, false, true, 0, 0); - data.removedItemsCache = new Cache( - name + "_" + txnId + "_removes", - maxCacheSize, false, true, 0, 0); - try - { - cacheManager.addCache(data.updatedItemsCache); - cacheManager.addCache(data.removedItemsCache); - } - catch (CacheException e) - { - throw new AlfrescoRuntimeException("Failed to add txn caches to manager", e); - } - finally - { - // ensure that we get the transaction callbacks as we have bound the unique - // transactional caches to a common manager - AlfrescoTransactionSupport.bindListener(this); - } + data.updatedItemsCache = new LRUMap(maxCacheSize); + data.removedItemsCache = new HashSet(maxCacheSize * 2); + + // ensure that we get the transaction callbacks as we have bound the unique + // transactional caches to a common manager + AlfrescoTransactionSupport.bindListener(this); AlfrescoTransactionSupport.bindResource(resourceKeyTxnData, data); } return data; @@ -266,9 +243,9 @@ public class TransactionalCache keys.addAll(backingKeys); } // add keys - keys.addAll((Collection) txnData.updatedItemsCache.getKeys()); + keys.addAll(txnData.updatedItemsCache.keySet()); // remove keys - keys.removeAll((Collection) txnData.removedItemsCache.getKeys()); + keys.removeAll(txnData.removedItemsCache); } else { @@ -324,7 +301,7 @@ public class TransactionalCache if (!txnData.isClearOn) // deletions cache only useful before a clear { // check to see if the key is present in the transaction's removed items - if (txnData.removedItemsCache.get(key) != null) + if (txnData.removedItemsCache.contains(key)) { // it has been removed in this transaction if (isDebugEnabled) @@ -338,10 +315,9 @@ public class TransactionalCache } // check for the item in the transaction's new/updated items - Element element = txnData.updatedItemsCache.get(key); - if (element != null) + CacheBucket bucket = (CacheBucket) txnData.updatedItemsCache.get(key); + if (bucket != null) { - CacheBucket bucket = (CacheBucket) element.getValue(); V value = bucket.getValue(); // element was found in transaction-specific updates/additions if (isDebugEnabled) @@ -429,7 +405,7 @@ public class TransactionalCache { // we have an active transaction - add the item into the updated cache for this transaction // are we in an overflow condition? - if (txnData.updatedItemsCache.getMemoryStoreSize() >= maxCacheSize) + if (txnData.updatedItemsCache.isFull()) { // overflow about to occur or has occured - we can only guarantee non-stale // data by clearing the shared cache after the transaction. Also, the @@ -456,8 +432,7 @@ public class TransactionalCache // The value didn't exist before bucket = new NewCacheBucket(nullMarker, value); } - Element element = new Element(key, bucket); - txnData.updatedItemsCache.put(element); + txnData.updatedItemsCache.put(key, bucket); // remove the item from the removed cache, if present txnData.removedItemsCache.remove(key); // done @@ -517,7 +492,7 @@ public class TransactionalCache else { // are we in an overflow condition? - if (txnData.removedItemsCache.getMemoryStoreSize() >= maxCacheSize) + if (txnData.removedItemsCache.size() >= maxCacheSize) { // overflow about to occur or has occured - we can only guarantee non-stale // data by clearing the shared cache after the transaction. Also, the @@ -539,9 +514,7 @@ public class TransactionalCache else { // Create a bucket to remove the value from the shared cache - CacheBucket removeBucket = new RemoveCacheBucket(existingValue); - Element element = new Element(key, removeBucket); - txnData.removedItemsCache.put(element); + txnData.removedItemsCache.add(key); } } } @@ -590,8 +563,8 @@ public class TransactionalCache // and also serves to ensure that the shared cache will be ignored // for the remainder of the transaction txnData.isClearOn = true; - txnData.updatedItemsCache.removeAll(); - txnData.removedItemsCache.removeAll(); + txnData.updatedItemsCache.clear(); + txnData.removedItemsCache.clear(); } } else // no transaction @@ -654,23 +627,22 @@ public class TransactionalCache // transfer any removed items // any removed items will have also been removed from the in-transaction updates // propogate the deletes to the shared cache - List keys = txnData.removedItemsCache.getKeys(); - for (Serializable key : keys) + for (Serializable key : txnData.removedItemsCache) { sharedCache.remove(key); } if (isDebugEnabled) { - logger.debug("Removed " + keys.size() + " values from shared cache"); + logger.debug("Removed " + txnData.removedItemsCache.size() + " values from shared cache"); } } // transfer updates - List keys = txnData.updatedItemsCache.getKeys(); - for (Serializable key : keys) + Set keys = (Set) txnData.updatedItemsCache.keySet(); + for (Map.Entry> entry : (Set>>) txnData.updatedItemsCache.entrySet()) { - Element element = txnData.updatedItemsCache.get(key); - CacheBucket bucket = (CacheBucket) element.getObjectValue(); + K key = entry.getKey(); + CacheBucket bucket = entry.getValue(); bucket.doPostCommit(sharedCache, key); } if (isDebugEnabled) @@ -705,8 +677,6 @@ public class TransactionalCache */ private void removeCaches(TransactionData txnData) { - cacheManager.removeCache(txnData.updatedItemsCache.getName()); - cacheManager.removeCache(txnData.removedItemsCache.getName()); txnData.isClosed = true; } @@ -830,30 +800,11 @@ public class TransactionalCache } } - /** - * Data holder to keep track of cache removals. This bucket assumes the previous existence - * of an entry in the shared cache. - */ - private static class RemoveCacheBucket extends UpdateCacheBucket - { - private static final long serialVersionUID = -7736719065158540252L; - - public RemoveCacheBucket(BV originalValue) - { - super(originalValue, null); - } - public void doPostCommit(SimpleCache sharedCache, Serializable key) - { - // We remove the shared entry whether it has moved on or not - sharedCache.remove(key); - } - } - /** Data holder to bind data to the transaction */ private class TransactionData { - private Cache updatedItemsCache; - private Cache removedItemsCache; + private LRUMap updatedItemsCache; + private Set removedItemsCache; private boolean haveIssuedFullWarning; private boolean isClearOn; private boolean isClosed; diff --git a/source/test-resources/cache-test/cache-test-config.xml b/source/test-resources/cache-test/cache-test-config.xml new file mode 100644 index 0000000000..a8a1a4192b --- /dev/null +++ b/source/test-resources/cache-test/cache-test-config.xml @@ -0,0 +1,41 @@ + + + + + + + + + + + + + + \ No newline at end of file diff --git a/source/test-resources/cache-test-context.xml b/source/test-resources/cache-test/cache-test-context.xml similarity index 73% rename from source/test-resources/cache-test-context.xml rename to source/test-resources/cache-test/cache-test-context.xml index 88f12b190b..d9cfdd5c24 100644 --- a/source/test-resources/cache-test-context.xml +++ b/source/test-resources/cache-test/cache-test-context.xml @@ -4,15 +4,18 @@ - + - classpath:alfresco/ehcache-default.xml + classpath:cache-test/cache-test-config.xml + + + cache1 @@ -23,6 +26,9 @@ + + + backingCache @@ -34,20 +40,20 @@ - - - transactionalCache - 20000 + 200000 + + + objectCache