You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@jackrabbit.apache.org by ju...@apache.org on 2010/10/02 13:02:07 UTC

svn commit: r1003773 - in /jackrabbit/trunk/jackrabbit-core/src: main/java/org/apache/jackrabbit/core/cache/ main/java/org/apache/jackrabbit/core/persistence/bundle/ main/java/org/apache/jackrabbit/core/persistence/pool/ main/java/org/apache/jackrabbit...

Author: jukka
Date: Sat Oct  2 11:02:06 2010
New Revision: 1003773

URL: http://svn.apache.org/viewvc?rev=1003773&view=rev
Log:
JCR-2699: Improve read/write concurrency

Revert revision 1003542 until I have time to solve the NPE issue.

Added:
    jackrabbit/trunk/jackrabbit-core/src/main/java/org/apache/jackrabbit/core/persistence/util/BundleCache.java
      - copied unchanged from r1003541, jackrabbit/trunk/jackrabbit-core/src/main/java/org/apache/jackrabbit/core/persistence/util/BundleCache.java
Removed:
    jackrabbit/trunk/jackrabbit-core/src/main/java/org/apache/jackrabbit/core/cache/LRUCache.java
    jackrabbit/trunk/jackrabbit-core/src/test/java/org/apache/jackrabbit/core/cache/
Modified:
    jackrabbit/trunk/jackrabbit-core/src/main/java/org/apache/jackrabbit/core/persistence/bundle/AbstractBundlePersistenceManager.java
    jackrabbit/trunk/jackrabbit-core/src/main/java/org/apache/jackrabbit/core/persistence/pool/AbstractBundlePersistenceManager.java
    jackrabbit/trunk/jackrabbit-core/src/main/java/org/apache/jackrabbit/core/state/ItemStateCache.java
    jackrabbit/trunk/jackrabbit-core/src/main/java/org/apache/jackrabbit/core/state/ItemStateReferenceCache.java
    jackrabbit/trunk/jackrabbit-core/src/main/java/org/apache/jackrabbit/core/state/MLRUItemStateCache.java
    jackrabbit/trunk/jackrabbit-core/src/main/java/org/apache/jackrabbit/core/state/ManagedMLRUItemStateCacheFactory.java

Modified: jackrabbit/trunk/jackrabbit-core/src/main/java/org/apache/jackrabbit/core/persistence/bundle/AbstractBundlePersistenceManager.java
URL: http://svn.apache.org/viewvc/jackrabbit/trunk/jackrabbit-core/src/main/java/org/apache/jackrabbit/core/persistence/bundle/AbstractBundlePersistenceManager.java?rev=1003773&r1=1003772&r2=1003773&view=diff
==============================================================================
--- jackrabbit/trunk/jackrabbit-core/src/main/java/org/apache/jackrabbit/core/persistence/bundle/AbstractBundlePersistenceManager.java (original)
+++ jackrabbit/trunk/jackrabbit-core/src/main/java/org/apache/jackrabbit/core/persistence/bundle/AbstractBundlePersistenceManager.java Sat Oct  2 11:02:06 2010
@@ -18,7 +18,6 @@ package org.apache.jackrabbit.core.persi
 
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
-import org.apache.jackrabbit.core.cache.LRUCache;
 import org.apache.jackrabbit.core.fs.FileSystemResource;
 import org.apache.jackrabbit.core.fs.FileSystem;
 import org.apache.jackrabbit.core.state.ItemState;
@@ -38,6 +37,7 @@ import org.apache.jackrabbit.core.persis
 import org.apache.jackrabbit.core.persistence.PersistenceManager;
 import org.apache.jackrabbit.core.util.StringIndex;
 import org.apache.jackrabbit.core.persistence.util.BLOBStore;
+import org.apache.jackrabbit.core.persistence.util.BundleCache;
 import org.apache.jackrabbit.core.persistence.util.FileBasedIndex;
 import org.apache.jackrabbit.core.persistence.util.LRUNodeIdCache;
 import org.apache.jackrabbit.core.persistence.util.NodePropBundle;
@@ -68,7 +68,7 @@ import javax.jcr.PropertyType;
  * included in the bundle but generated when required.
  * <p/>
  * In order to increase performance, there are 2 caches maintained. One is the
- * bundle cache that caches already loaded bundles. The other is the
+ * {@link BundleCache} that caches already loaded bundles. The other is the
  * {@link LRUNodeIdCache} that caches non-existent bundles. This is useful
  * because a lot of {@link #exists(NodeId)} calls are issued that would result
  * in a useless SQL execution if the desired bundle does not exist.
@@ -103,7 +103,7 @@ public abstract class AbstractBundlePers
     private StringIndex nameIndex;
 
     /** the cache of loaded bundles */
-    private LRUCache<NodeId, NodePropBundle> bundles;
+    private BundleCache bundles;
 
     /** the cache of non-existent bundles */
     private LRUNodeIdCache missing;
@@ -387,7 +387,7 @@ public abstract class AbstractBundlePers
     public void init(PMContext context) throws Exception {
         this.context = context;
         // init bundle cache
-        bundles = new LRUCache<NodeId, NodePropBundle>(bundleCacheSize);
+        bundles = new BundleCache(bundleCacheSize);
         missing = new LRUNodeIdCache();
     }
     
@@ -656,7 +656,7 @@ public abstract class AbstractBundlePers
                 bundle = loadBundle(id);
                 if (bundle != null) {
                     bundle.markOld();
-                    bundles.put(id, bundle, bundle.getSize());
+                    bundles.put(bundle);
                 } else {
                     missing.put(id);
                 }
@@ -692,9 +692,8 @@ public abstract class AbstractBundlePers
         missing.remove(bundle.getId());
         // only put to cache if already exists. this is to ensure proper overwrite
         // and not creating big contention during bulk loads
-        if (bundles.containsKey(bundle.getId())) {
-            bundles.remove(bundle.getId());
-            bundles.put(bundle.getId(), bundle, bundle.getSize());
+        if (bundles.contains(bundle.getId())) {
+            bundles.put(bundle);
         }
     }
 

Modified: jackrabbit/trunk/jackrabbit-core/src/main/java/org/apache/jackrabbit/core/persistence/pool/AbstractBundlePersistenceManager.java
URL: http://svn.apache.org/viewvc/jackrabbit/trunk/jackrabbit-core/src/main/java/org/apache/jackrabbit/core/persistence/pool/AbstractBundlePersistenceManager.java?rev=1003773&r1=1003772&r2=1003773&view=diff
==============================================================================
--- jackrabbit/trunk/jackrabbit-core/src/main/java/org/apache/jackrabbit/core/persistence/pool/AbstractBundlePersistenceManager.java (original)
+++ jackrabbit/trunk/jackrabbit-core/src/main/java/org/apache/jackrabbit/core/persistence/pool/AbstractBundlePersistenceManager.java Sat Oct  2 11:02:06 2010
@@ -24,7 +24,6 @@ import javax.jcr.PropertyType;
 
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
-import org.apache.jackrabbit.core.cache.LRUCache;
 import org.apache.jackrabbit.core.fs.FileSystemResource;
 import org.apache.jackrabbit.core.fs.FileSystem;
 import org.apache.jackrabbit.core.id.ItemId;
@@ -35,6 +34,7 @@ import org.apache.jackrabbit.core.persis
 import org.apache.jackrabbit.core.persistence.PMContext;
 import org.apache.jackrabbit.core.persistence.PersistenceManager;
 import org.apache.jackrabbit.core.persistence.util.BLOBStore;
+import org.apache.jackrabbit.core.persistence.util.BundleCache;
 import org.apache.jackrabbit.core.persistence.util.FileBasedIndex;
 import org.apache.jackrabbit.core.persistence.util.LRUNodeIdCache;
 import org.apache.jackrabbit.core.persistence.util.NodePropBundle;
@@ -68,7 +68,7 @@ import org.apache.jackrabbit.spi.commons
  * included in the bundle but generated when required.
  * <p/>
  * In order to increase performance, there are 2 caches maintained. One is the
- * bundle cache that caches already loaded bundles. The other is the
+ * {@link BundleCache} that caches already loaded bundles. The other is the
  * {@link LRUNodeIdCache} that caches non-existent bundles. This is useful
  * because a lot of {@link #exists(NodeId)} calls are issued that would result
  * in a useless SQL execution if the desired bundle does not exist.
@@ -103,7 +103,7 @@ public abstract class AbstractBundlePers
     private StringIndex nameIndex;
 
     /** the cache of loaded bundles */
-    private LRUCache<NodeId, NodePropBundle> bundles;
+    private BundleCache bundles;
 
     /** the cache of non-existent bundles */
     private LRUNodeIdCache missing;
@@ -387,7 +387,7 @@ public abstract class AbstractBundlePers
     public void init(PMContext context) throws Exception {
         this.context = context;
         // init bundle cache
-        bundles = new LRUCache<NodeId, NodePropBundle>(bundleCacheSize);
+        bundles = new BundleCache(bundleCacheSize);
         missing = new LRUNodeIdCache();
     }
 
@@ -654,7 +654,7 @@ public abstract class AbstractBundlePers
                 bundle = loadBundle(id);
                 if (bundle != null) {
                     bundle.markOld();
-                    bundles.put(id, bundle, bundle.getSize());
+                    bundles.put(bundle);
                 } else {
                     missing.put(id);
                 }
@@ -690,9 +690,8 @@ public abstract class AbstractBundlePers
         missing.remove(bundle.getId());
         // only put to cache if already exists. this is to ensure proper overwrite
         // and not creating big contention during bulk loads
-        if (bundles.containsKey(bundle.getId())) {
-            bundles.remove(bundle.getId());
-            bundles.put(bundle.getId(), bundle, bundle.getSize());
+        if (bundles.contains(bundle.getId())) {
+            bundles.put(bundle);
         }
     }
 

Modified: jackrabbit/trunk/jackrabbit-core/src/main/java/org/apache/jackrabbit/core/state/ItemStateCache.java
URL: http://svn.apache.org/viewvc/jackrabbit/trunk/jackrabbit-core/src/main/java/org/apache/jackrabbit/core/state/ItemStateCache.java?rev=1003773&r1=1003772&r2=1003773&view=diff
==============================================================================
--- jackrabbit/trunk/jackrabbit-core/src/main/java/org/apache/jackrabbit/core/state/ItemStateCache.java (original)
+++ jackrabbit/trunk/jackrabbit-core/src/main/java/org/apache/jackrabbit/core/state/ItemStateCache.java Sat Oct  2 11:02:06 2010
@@ -83,6 +83,14 @@ public interface ItemStateCache {
     boolean isEmpty();
 
     /**
+     * Informs the cache that the item was modified and the cache might need to
+     * recalculate the items caching weight.
+     *
+     * @param id the id of the item that was modified.
+     */
+    void update(ItemId id);
+
+    /**
      * Informs the cache that it is no longer in use.
      */
     void dispose();

Modified: jackrabbit/trunk/jackrabbit-core/src/main/java/org/apache/jackrabbit/core/state/ItemStateReferenceCache.java
URL: http://svn.apache.org/viewvc/jackrabbit/trunk/jackrabbit-core/src/main/java/org/apache/jackrabbit/core/state/ItemStateReferenceCache.java?rev=1003773&r1=1003772&r2=1003773&view=diff
==============================================================================
--- jackrabbit/trunk/jackrabbit-core/src/main/java/org/apache/jackrabbit/core/state/ItemStateReferenceCache.java (original)
+++ jackrabbit/trunk/jackrabbit-core/src/main/java/org/apache/jackrabbit/core/state/ItemStateReferenceCache.java Sat Oct  2 11:02:06 2010
@@ -162,6 +162,14 @@ public class ItemStateReferenceCache imp
     /**
      * {@inheritDoc}
      */
+    public synchronized void update(ItemId id) {
+        // delegate
+        cache.update(id);
+    }
+
+    /**
+     * {@inheritDoc}
+     */
     public synchronized boolean isEmpty() {
         // check primary cache
         return refs.isEmpty();

Modified: jackrabbit/trunk/jackrabbit-core/src/main/java/org/apache/jackrabbit/core/state/MLRUItemStateCache.java
URL: http://svn.apache.org/viewvc/jackrabbit/trunk/jackrabbit-core/src/main/java/org/apache/jackrabbit/core/state/MLRUItemStateCache.java?rev=1003773&r1=1003772&r2=1003773&view=diff
==============================================================================
--- jackrabbit/trunk/jackrabbit-core/src/main/java/org/apache/jackrabbit/core/state/MLRUItemStateCache.java (original)
+++ jackrabbit/trunk/jackrabbit-core/src/main/java/org/apache/jackrabbit/core/state/MLRUItemStateCache.java Sat Oct  2 11:02:06 2010
@@ -16,9 +16,14 @@
  */
 package org.apache.jackrabbit.core.state;
 
+import java.util.ArrayList;
+import java.util.LinkedHashMap;
+import java.util.List;
+import java.util.Map;
+
 import org.apache.commons.collections.map.LinkedMap;
-import org.apache.jackrabbit.core.cache.CacheManager;
-import org.apache.jackrabbit.core.cache.LRUCache;
+import org.apache.jackrabbit.core.cache.Cache;
+import org.apache.jackrabbit.core.cache.CacheAccessListener;
 import org.apache.jackrabbit.core.id.ItemId;
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
@@ -33,58 +38,139 @@ import org.slf4j.LoggerFactory;
  * TODO rename class to something more appropriate, e.g. FIFOItemSateCache since
  * it doesn't use a LRU eviction policy anymore.
  */
-public class MLRUItemStateCache implements ItemStateCache {
-
+public class MLRUItemStateCache implements ItemStateCache, Cache {
     /** Logger instance */
     private static Logger log = LoggerFactory.getLogger(MLRUItemStateCache.class);
 
     /** default maximum memory to use */
     public static final int DEFAULT_MAX_MEM = 4 * 1024 * 1024;
 
+    /** the amount of memory the entries use */
+    private volatile long totalMem;
+
+    /** the maximum of memory the cache may use */
+    private volatile long maxMem;
+
     /** the number of writes */
-    private volatile long numWrites = 0;
+    private volatile long numWrites;
+
+    /** the access count */
+    private volatile long accessCount;
 
-    private final LRUCache<ItemId, ItemState> cache =
-        new LRUCache<ItemId, ItemState>(DEFAULT_MAX_MEM);
+    /** the cache access listeners */
+    private CacheAccessListener accessListener;
 
-    public MLRUItemStateCache(CacheManager cacheMgr) {
-        cacheMgr.add(cache);
-        cache.setAccessListener(cacheMgr);
+    /**
+     * A cache for <code>ItemState</code> instances
+     */
+    private final Map<ItemId, Entry> cache;
+
+    /**
+     * Constructs a new, empty <code>ItemStateCache</code> with a maximum amount
+     * of memory of {@link #DEFAULT_MAX_MEM}.
+     */
+    public MLRUItemStateCache() {
+        this(DEFAULT_MAX_MEM);
     }
 
-    //-------------------------------------------------------< ItemStateCache >
+    /**
+     * Constructs a new, empty <code>ItemStateCache</code> with the specified
+     * maximum memory.
+     *
+     * @param maxMem the maximum amount of memory this cache may use.
+     */
+    @SuppressWarnings("serial")
+    private MLRUItemStateCache(int maxMem) {
+        this.maxMem = maxMem;
+        this.cache = new LinkedHashMap<ItemId, MLRUItemStateCache.Entry>(
+                maxMem / 1024, 0.75f, true /* access-ordered */) {
+            @Override
+            protected boolean removeEldestEntry(Map.Entry<ItemId, Entry> e) {
+                long maxMem = MLRUItemStateCache.this.maxMem;
+                if (totalMem <= maxMem) {
+                    return false;
+                } else if (totalMem - e.getValue().size <= maxMem) {
+                    totalMem -= e.getValue().size;
+                    return true;
+                } else {
+                    shrink();
+                    return false;
+                }
+            }
+        };
+    }
 
+    //-------------------------------------------------------< ItemStateCache >
     /**
      * {@inheritDoc}
      */
     public boolean isCached(ItemId id) {
-        return cache.containsKey(id);
+        synchronized (cache) {
+            return cache.containsKey(id);
+        }
     }
 
     /**
      * {@inheritDoc}
      */
     public ItemState retrieve(ItemId id) {
-        return cache.get(id);
+        touch();
+        synchronized (cache) {
+            Entry entry = cache.get(id);
+            if (entry != null) {
+                return entry.state;
+            } else {
+                return null;
+            }
+        }
     }
 
     /**
      * {@inheritDoc}
      */
     public ItemState[] retrieveAll() {
-        return cache.values();
+        synchronized (cache) {
+            ItemState[] states = new ItemState[cache.size()];
+            int i = 0;
+            for (Entry entry : cache.values()) {
+                states[i++] = entry.state;
+            }
+            return states;
+        }
     }
 
     /**
      * {@inheritDoc}
      */
-    public synchronized void cache(ItemState state) {
-        cache.put(state.getId(), state, state.calculateMemoryFootprint());
+    public void update(ItemId id) {
+        touch();
+        synchronized (cache) {
+            Entry entry = cache.get(id);
+            if (entry != null) {
+                totalMem -= entry.size;
+                entry.recalc();
+                totalMem += entry.size;
+            }
+        }
+    }
 
-        if (numWrites++ % 10000 == 0 && log.isDebugEnabled()) {
-            log.debug("Item state cache size: {}% of {} bytes",
-                    cache.getMemoryUsed() * 100 / cache.getMaxMemorySize(),
-                    cache.getMaxMemorySize());
+    /**
+     * {@inheritDoc}
+     */
+    public void cache(ItemState state) {
+        touch();
+        synchronized (cache) {
+            ItemId id = state.getId();
+            if (cache.containsKey(id)) {
+                log.warn("overwriting cached entry " + id);
+                evict(id);
+            }
+            Entry entry = new Entry(state);
+            totalMem += entry.size;
+            cache.put(id, entry);
+            if (numWrites++ % 10000 == 0 && log.isDebugEnabled()) {
+                log.debug(this + " size=" + cache.size() + ", " + totalMem + "/" + maxMem);
+            }
         }
     }
 
@@ -92,28 +178,135 @@ public class MLRUItemStateCache implemen
      * {@inheritDoc}
      */
     public void evict(ItemId id) {
-        cache.remove(id);
+        touch();
+        synchronized (cache) {
+            Entry entry = cache.remove(id);
+            if (entry != null) {
+                totalMem -= entry.size;
+            }
+        }
     }
 
     /**
      * {@inheritDoc}
      */
     public void evictAll() {
-        cache.clear();
+        synchronized (cache) {
+            cache.clear();
+            totalMem = 0;
+        }
     }
 
     /**
      * {@inheritDoc}
      */
     public boolean isEmpty() {
-        return cache.isEmpty();
+        synchronized (cache) {
+            return cache.isEmpty();
+        }
+    }
+
+    private void touch() {
+        accessCount++;
+        if ((accessCount % CacheAccessListener.ACCESS_INTERVAL) == 0) {
+            if (accessListener != null) {
+                accessListener.cacheAccessed();
+            }
+        }
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    public long getAccessCount() {
+        return accessCount;
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    public long getMaxMemorySize() {
+        return maxMem;
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    public long getMemoryUsed() {
+        return totalMem;
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    public void resetAccessCount() {
+        synchronized (cache) {
+            accessCount = 0;
+        }
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    public void setMaxMemorySize(long size) {
+        synchronized (cache) {
+            this.maxMem = size;
+
+            // remove items, if too many
+            if (totalMem > maxMem) {
+                shrink();
+            }
+        }
+    }
+
+    private void shrink() {
+        List<Map.Entry<ItemId, Entry>> list =
+            new ArrayList<Map.Entry<ItemId, Entry>>(cache.entrySet());
+        for (int i = list.size() - 1; totalMem > maxMem && i >= 0; i--) {
+            Map.Entry<ItemId, Entry> last = list.get(i);
+            totalMem -= last.getValue().size;
+            cache.remove(last.getKey());
+        }
+    }
+
+    /**
+     * Set the cache access listener. Only one listener per cache is supported.
+     *
+     * @param listener the new listener
+     */
+    public void setAccessListener(CacheAccessListener listener) {
+        this.accessListener = listener;
     }
 
     /**
      * {@inheritDoc}
      */
     public void dispose() {
-        cache.dispose();
+        synchronized (cache) {
+            if (accessListener != null) {
+                accessListener.disposeCache(this);
+            }
+        }
+    }
+
+
+    /**
+     * Internal cache entry.
+     */
+    private static class Entry {
+
+        private final ItemState state;
+
+        private long size;
+
+        public Entry(ItemState state) {
+            this.state = state;
+            this.size = 64 + state.calculateMemoryFootprint();
+        }
+
+        public void recalc() {
+            size = 64 + state.calculateMemoryFootprint();
+        }
     }
 
 }

Modified: jackrabbit/trunk/jackrabbit-core/src/main/java/org/apache/jackrabbit/core/state/ManagedMLRUItemStateCacheFactory.java
URL: http://svn.apache.org/viewvc/jackrabbit/trunk/jackrabbit-core/src/main/java/org/apache/jackrabbit/core/state/ManagedMLRUItemStateCacheFactory.java?rev=1003773&r1=1003772&r2=1003773&view=diff
==============================================================================
--- jackrabbit/trunk/jackrabbit-core/src/main/java/org/apache/jackrabbit/core/state/ManagedMLRUItemStateCacheFactory.java (original)
+++ jackrabbit/trunk/jackrabbit-core/src/main/java/org/apache/jackrabbit/core/state/ManagedMLRUItemStateCacheFactory.java Sat Oct  2 11:02:06 2010
@@ -41,7 +41,10 @@ public class ManagedMLRUItemStateCacheFa
      * Create a new cache instance and link it to the cache manager.
      */
     public ItemStateCache newItemStateCache() {
-        return new MLRUItemStateCache(cacheMgr);
+        MLRUItemStateCache cache = new MLRUItemStateCache();
+        cacheMgr.add(cache);
+        cache.setAccessListener(cacheMgr);
+        return cache;
     }
 
 }