You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@jackrabbit.apache.org by th...@apache.org on 2011/09/09 18:48:18 UTC

svn commit: r1167286 - in /jackrabbit/sandbox/microkernel/src: main/java/org/apache/jackrabbit/mk/util/Cache.java test/java/org/apache/jackrabbit/mk/concurrent/ test/java/org/apache/jackrabbit/mk/concurrent/ConcurrentCacheTest.java

Author: thomasm
Date: Fri Sep  9 16:48:18 2011
New Revision: 1167286

URL: http://svn.apache.org/viewvc?rev=1167286&view=rev
Log:
Cache (reads from the storage backend don't block other reads; measure size in bytes)

Added:
    jackrabbit/sandbox/microkernel/src/main/java/org/apache/jackrabbit/mk/util/Cache.java
    jackrabbit/sandbox/microkernel/src/test/java/org/apache/jackrabbit/mk/concurrent/
    jackrabbit/sandbox/microkernel/src/test/java/org/apache/jackrabbit/mk/concurrent/ConcurrentCacheTest.java

Added: jackrabbit/sandbox/microkernel/src/main/java/org/apache/jackrabbit/mk/util/Cache.java
URL: http://svn.apache.org/viewvc/jackrabbit/sandbox/microkernel/src/main/java/org/apache/jackrabbit/mk/util/Cache.java?rev=1167286&view=auto
==============================================================================
--- jackrabbit/sandbox/microkernel/src/main/java/org/apache/jackrabbit/mk/util/Cache.java (added)
+++ jackrabbit/sandbox/microkernel/src/main/java/org/apache/jackrabbit/mk/util/Cache.java Fri Sep  9 16:48:18 2011
@@ -0,0 +1,146 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.jackrabbit.mk.util;
+
+import java.util.LinkedHashMap;
+import java.util.Map;
+import java.util.concurrent.atomic.AtomicInteger;
+
+public class Cache<K, V extends Cache.Value> {
+
+    int maxMemoryBytes;
+    AtomicInteger memoryUsed = new AtomicInteger();
+    private final Backend<K, V> backend;
+
+    private final Map<K, V> map = new LinkedHashMap<K, V>() {
+
+        private static final long serialVersionUID = 1L;
+
+        protected boolean removeEldestEntry(Map.Entry<K, V> eldest) {
+            if (memoryUsed.get() < maxMemoryBytes) {
+                return false;
+            }
+            int memory = eldest.getValue().getMemory();
+            memoryUsed.addAndGet(-memory);
+            return true;
+        }
+
+    };
+
+    private Cache(Backend<K, V> backend, int maxMemoryBytes) {
+        this.backend = backend;
+        this.maxMemoryBytes = maxMemoryBytes;
+    }
+
+    public void put(K key, V value) {
+        int memory = value.getMemory();
+        // only add elements that are smaller than half the cache size
+        if (memory < maxMemoryBytes / 2) {
+            memoryUsed.addAndGet(memory);
+            synchronized (map) {
+                map.put(key, value);
+            }
+        }
+    }
+
+    /**
+     * Get the element in the cache if one exists, or add it to the cache if not.
+     *
+     * @param key the key
+     * @param value the value
+     * @return the cached element
+     */
+    public V replace(K key, V value) {
+        synchronized (map) {
+            if (map.containsKey(key)) {
+                return map.get(key);
+            }
+        }
+        put(key, value);
+        return value;
+    }
+
+    public V get(K key) {
+        synchronized (map) {
+            V value = map.get(key);
+            if (value != null) {
+                // object was in the cache - good
+                return value;
+            }
+        }
+        // synchronize on the backend when not in the cache
+        // to ensure only one thread accessed the backend
+        // and loads the object
+        synchronized (backend) {
+            // another thread might have added it in the meantime
+            V value;
+            synchronized (map) {
+                value = map.get(key);
+            }
+            if (value == null) {
+                value = backend.load(key);
+                put(key, value);
+            }
+            return value;
+        }
+    }
+
+    /**
+     * A cacheable object.
+     */
+    public static interface Value {
+
+        /**
+         * Get the memory required in bytes. The method must always return the
+         * same value once the element is in the cache.
+         *
+         * @return the memory used in bytes
+         */
+        int getMemory();
+
+    }
+
+    /**
+     * A cache backend that can load objects from persistent storage.
+     *
+     * @param <K> the key class
+     * @param <V> the value class
+     */
+    public static interface Backend<K, V> {
+
+        /**
+         * Load the object. The method does not need to be synchronized
+         * (it is synchronized in the cache)
+         *
+         * @param key the key
+         * @return the value
+         */
+        V load(K key);
+
+    }
+
+    public static <K, V extends Cache.Value> Cache<K, V> newInstance(Backend<K, V> backend, int maxMemoryBytes) {
+        return new Cache<K, V>(backend, maxMemoryBytes);
+    }
+
+    public void clear() {
+        synchronized (map) {
+            map.clear();
+        }
+    }
+
+}

Added: jackrabbit/sandbox/microkernel/src/test/java/org/apache/jackrabbit/mk/concurrent/ConcurrentCacheTest.java
URL: http://svn.apache.org/viewvc/jackrabbit/sandbox/microkernel/src/test/java/org/apache/jackrabbit/mk/concurrent/ConcurrentCacheTest.java?rev=1167286&view=auto
==============================================================================
--- jackrabbit/sandbox/microkernel/src/test/java/org/apache/jackrabbit/mk/concurrent/ConcurrentCacheTest.java (added)
+++ jackrabbit/sandbox/microkernel/src/test/java/org/apache/jackrabbit/mk/concurrent/ConcurrentCacheTest.java Fri Sep  9 16:48:18 2011
@@ -0,0 +1,75 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.jackrabbit.mk.concurrent;
+
+import java.util.concurrent.atomic.AtomicInteger;
+import org.apache.jackrabbit.mk.util.Cache;
+
+/**
+ * Tests the cache implementation.
+ */
+public class ConcurrentCacheTest extends ConcurrentTest implements Cache.Backend<Integer, ConcurrentCacheTest.Data> {
+
+    Cache<Integer, Data> cache = Cache.newInstance(this, 5);
+    AtomicInteger counter = new AtomicInteger();
+    volatile int value;
+
+    public void setUp() throws Exception {
+        // nothing to do
+    }
+
+    public void tearDown() throws InterruptedException {
+        // nothing to do
+    }
+
+    void access() {
+        try {
+            int k = value++ % 10;
+            Data v = cache.get(k);
+            assertEquals(k, v.value);
+        } catch (Throwable t) {
+            lastException = new Exception(t);
+        }
+    }
+
+    public Data load(Integer key) {
+        int start = counter.get();
+        try {
+            Thread.sleep(1);
+        } catch (InterruptedException e) {
+            // ignore
+        }
+        if (counter.getAndIncrement() != start) {
+            throw new AssertionError("Concurrent load");
+        }
+        return new Data(key);
+    }
+
+    static class Data implements Cache.Value {
+
+        int value;
+
+        Data(int value) {
+            this.value = value;
+        }
+
+        public int getMemory() {
+            return 1;
+        }
+
+    }
+}