Author: thomasm
Date: Tue Jan 7 13:25:40 2014
New Revision: 1556209
URL: http://svn.apache.org/r1556209
Log:
OAK-1094 CacheLIRS implementation incomplete (asMap, refresh, some tests)
Added:
jackrabbit/oak/trunk/oak-core/src/test/java/org/apache/jackrabbit/oak/cache/
jackrabbit/oak/trunk/oak-core/src/test/java/org/apache/jackrabbit/oak/cache/CacheTest.java
Modified:
jackrabbit/oak/trunk/oak-core/src/main/java/org/apache/jackrabbit/oak/cache/CacheLIRS.java
jackrabbit/oak/trunk/oak-core/src/test/java/org/apache/jackrabbit/oak/kernel/KernelNodeStoreCacheTest.java
Modified:
jackrabbit/oak/trunk/oak-core/src/main/java/org/apache/jackrabbit/oak/cache/CacheLIRS.java
URL:
http://svn.apache.org/viewvc/jackrabbit/oak/trunk/oak-core/src/main/java/org/apache/jackrabbit/oak/cache/CacheLIRS.java?rev=1556209&r1=1556208&r2=1556209&view=diff
==============================================================================
---
jackrabbit/oak/trunk/oak-core/src/main/java/org/apache/jackrabbit/oak/cache/CacheLIRS.java
(original)
+++
jackrabbit/oak/trunk/oak-core/src/main/java/org/apache/jackrabbit/oak/cache/CacheLIRS.java
Tue Jan 7 13:25:40 2014
@@ -17,6 +17,7 @@
package org.apache.jackrabbit.oak.cache;
import java.util.ArrayList;
+import java.util.Collection;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
@@ -24,16 +25,20 @@ import java.util.Map;
import java.util.Set;
import java.util.concurrent.Callable;
import java.util.concurrent.ConcurrentMap;
-import java.util.concurrent.ConcurrentSkipListMap;
import java.util.concurrent.ExecutionException;
import javax.annotation.Nullable;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
import com.google.common.cache.CacheLoader;
import com.google.common.cache.CacheStats;
import com.google.common.cache.LoadingCache;
import com.google.common.cache.Weigher;
import com.google.common.collect.ImmutableMap;
+import com.google.common.util.concurrent.ListenableFuture;
+import com.google.common.util.concurrent.UncheckedExecutionException;
/**
* A scan resistant cache. It is meant to cache objects that are relatively
@@ -65,6 +70,8 @@ import com.google.common.collect.Immutab
* @param <V> the value type
*/
public class CacheLIRS<K, V> implements LoadingCache<K, V> {
+
+ private static final Logger LOG = LoggerFactory.getLogger(CacheLIRS.class);
/**
* The maximum memory this cache should use.
@@ -209,6 +216,32 @@ public class CacheLIRS<K, V> implements
return getSegment(hash).get(key, hash, valueLoader);
}
+ /**
+ * Get the value, loading it if needed.
+ * <p>
+ * If there is an exception loading, an UncheckedExecutionException is
+ * thrown.
+ *
+ * @param key the key
+ * @return the value
+ * @throws UncheckedExecutionException
+ */
+ @Override
+ public V getUnchecked(K key) {
+ try {
+ return get(key);
+ } catch (ExecutionException e) {
+ throw new UncheckedExecutionException(e);
+ }
+ }
+
+ /**
+ * Get the value, loading it if needed.
+ *
+ * @param key the key
+ * @return the value
+ * @throws ExecutionException
+ */
@Override
public V get(K key) throws ExecutionException {
int hash = getHash(key);
@@ -216,6 +249,45 @@ public class CacheLIRS<K, V> implements
}
/**
+ * Re-load the value for the given key.
+ * <p>
+ * If there is an exception while loading, it is logged and ignored. This
+ * method calls CacheLoader.reload, but synchronously replaces the old
+ * value.
+ *
+ * @param key the key
+ */
+ @Override
+ public void refresh(K key) {
+ int hash = getHash(key);
+ try {
+ getSegment(hash).refresh(key, hash, loader);
+ } catch (ExecutionException e) {
+ LOG.warn("Could not refresh value for key " + key, e);
+ }
+ }
+
+ V replace(K key, V value) {
+ int hash = getHash(key);
+ return getSegment(hash).replace(key, hash, value, sizeOf(key, value));
+ }
+
+ boolean replace(K key, V oldValue, V newValue) {
+ int hash = getHash(key);
+ return getSegment(hash).replace(key, hash, oldValue, newValue,
sizeOf(key, newValue));
+ }
+
+ boolean remove(Object key, Object value) {
+ int hash = getHash(key);
+ return getSegment(hash).remove(key, hash, value);
+ }
+
+ protected V putIfAbsent(K key, V value) {
+ int hash = getHash(key);
+ return getSegment(hash).putIfAbsent(key, hash, value, sizeOf(key,
value));
+ }
+
+ /**
* Get the value for the given key if the entry is cached. This method
* adjusts the internal state of the cache sometimes, to ensure commonly
* used entries stay in the cache.
@@ -252,11 +324,23 @@ public class CacheLIRS<K, V> implements
* @param key the key (may not be null)
*/
@Override
- public synchronized void invalidate(Object key) {
+ public void invalidate(Object key) {
int hash = getHash(key);
getSegment(hash).invalidate(key, hash);
}
+ /**
+ * Remove an entry. Both resident and non-resident entries can be
+ * removed.
+ *
+ * @param key the key (may not be null)
+ * @return the old value or null
+ */
+ public V remove(Object key) {
+ int hash = getHash(key);
+ return getSegment(hash).remove(key, hash);
+ }
+
@SuppressWarnings("unchecked")
@Override
public void invalidateAll(Iterable<?> keys) {
@@ -379,6 +463,29 @@ public class CacheLIRS<K, V> implements
}
return map.entrySet();
}
+
+ protected Collection<V> values() {
+ ArrayList<V> list = new ArrayList<V>();
+ for (K k : keySet()) {
+ V v = find(k).value;
+ if (v != null) {
+ list.add(v);
+ }
+ }
+ return list;
+ }
+
+ boolean containsValue(Object value) {
+ for (Segment<K, V> s : segments) {
+ for (K k : s.keySet()) {
+ V v = find(k).value;
+ if (v != null && v.equals(value)) {
+ return true;
+ }
+ }
+ }
+ return false;
+ }
/**
* Get the set of keys for resident entries.
@@ -445,6 +552,12 @@ public class CacheLIRS<K, V> implements
}
return x;
}
+
+ void clear() {
+ for (Segment<K, V> s : segments) {
+ s.clear();
+ }
+ }
/**
* Get the list of keys. This method allows to read the internal state of
@@ -598,7 +711,7 @@ public class CacheLIRS<K, V> implements
clear();
}
- private void clear() {
+ synchronized void clear() {
// calculate the size of the map array
// assume a fill factor of at most 80%
@@ -762,6 +875,65 @@ public class CacheLIRS<K, V> implements
}
return value;
}
+
+ synchronized V replace(K key, int hash, V value, int memory) {
+ if (containsKey(key, hash)) {
+ return put(key, hash, value, memory);
+ }
+ return null;
+ }
+
+ synchronized boolean replace(K key, int hash, V oldValue, V newValue,
int memory) {
+ V old = get(key, hash);
+ if (old != null && old.equals(oldValue)) {
+ put(key, hash, newValue, memory);
+ return true;
+ }
+ return false;
+ }
+
+ synchronized boolean remove(Object key, int hash, Object value) {
+ V old = get(key, hash);
+ if (old != null && old.equals(value)) {
+ invalidate(key, hash);
+ return true;
+ }
+ return false;
+ }
+
+ synchronized V remove(Object key, int hash) {
+ V old = get(key, hash);
+ // even if old is null, there might still be a cold entry
+ invalidate(key, hash);
+ return old;
+ }
+
+ synchronized V putIfAbsent(K key, int hash, V value, int memory) {
+ V old = get(key, hash);
+ if (old == null) {
+ put(key, hash, value, memory);
+ return null;
+ }
+ return old;
+ }
+
+ synchronized void refresh(K key, int hash, CacheLoader<K, V> loader)
throws ExecutionException {
+ V value;
+ V old = get(key, hash);
+ long start = System.nanoTime();
+ try {
+ ListenableFuture<V> future = loader.reload(key, old);
+ value = future.get();
+ loadSuccessCount++;
+ } catch (Exception e) {
+ loadExceptionCount++;
+ throw new ExecutionException(e);
+ } finally {
+ long time = System.nanoTime() - start;
+ totalLoadTime += time;
+ }
+ put(key, hash, value, cache.sizeOf(key, value));
+ }
/**
* Add an entry to the cache. The entry may or may not exist in the
@@ -1192,14 +1364,96 @@ public class CacheLIRS<K, V> implements
@Override
public ConcurrentMap<K, V> asMap() {
- ConcurrentMap<K, V> map = new ConcurrentSkipListMap<K, V>();
- for (K key : keySet()) {
- V value = peek(key);
- if (value != null) {
- map.put(key, value);
+ return new ConcurrentMap<K, V>() {
+
+ @Override
+ public int size() {
+ long size = CacheLIRS.this.size();
+ return (int) Math.min(size, Integer.MAX_VALUE);
}
- }
- return map;
+
+ @Override
+ public boolean isEmpty() {
+ return CacheLIRS.this.size() == 0;
+ }
+
+ @Override
+ public boolean containsKey(Object key) {
+ return CacheLIRS.this.containsKey(key);
+ }
+
+ @Override
+ public boolean containsValue(Object value) {
+ return CacheLIRS.this.containsValue(value);
+ }
+
+ @SuppressWarnings("unchecked")
+ @Override
+ public V get(Object key) {
+ return CacheLIRS.this.getUnchecked((K) key);
+ }
+
+ @Override
+ public V put(K key, V value) {
+ return CacheLIRS.this.put(key, value, sizeOf(key, value));
+ }
+
+ @Override
+ public V remove(Object key) {
+ @SuppressWarnings("unchecked")
+ V old = CacheLIRS.this.getUnchecked((K) key);
+ CacheLIRS.this.invalidate(key);
+ return old;
+ }
+
+ @Override
+ public void putAll(Map<? extends K, ? extends V> m) {
+ for (Map.Entry<? extends K, ? extends V> e : m.entrySet()) {
+ put(e.getKey(), e.getValue());
+ }
+ }
+
+ @Override
+ public void clear() {
+ CacheLIRS.this.clear();
+ }
+
+ @Override
+ public Set<K> keySet() {
+ return CacheLIRS.this.keySet();
+ }
+
+ @Override
+ public Collection<V> values() {
+ return CacheLIRS.this.values();
+ }
+
+ @Override
+ public Set<java.util.Map.Entry<K, V>> entrySet() {
+ return CacheLIRS.this.entrySet();
+ }
+
+ @Override
+ public V putIfAbsent(K key, V value) {
+ return CacheLIRS.this.putIfAbsent(key, value);
+ }
+
+ @Override
+ public boolean remove(Object key, Object value) {
+ return CacheLIRS.this.remove(key, value);
+ }
+
+ @Override
+ public boolean replace(K key, V oldValue, V newValue) {
+ return CacheLIRS.this.replace(key, oldValue, newValue);
+ }
+
+ @Override
+ public V replace(K key, V value) {
+ return CacheLIRS.this.replace(key, value);
+ }
+
+ };
}
@Override
@@ -1215,28 +1469,18 @@ public class CacheLIRS<K, V> implements
}
@Override
- public V getUnchecked(K key) {
- // TODO Auto-generated method stub
- return null;
- }
-
- @Override
public ImmutableMap<K, V> getAll(Iterable<? extends K> keys)
throws ExecutionException {
- // TODO Auto-generated method stub
- return null;
+ throw new UnsupportedOperationException();
}
@Override
public V apply(K key) {
- // TODO Auto-generated method stub
- return null;
+ throw new UnsupportedOperationException();
}
- @Override
- public void refresh(K key) {
- // TODO Auto-generated method stub
-
+ public boolean isEmpty() {
+ return size() == 0;
}
}
Added:
jackrabbit/oak/trunk/oak-core/src/test/java/org/apache/jackrabbit/oak/cache/CacheTest.java
URL:
http://svn.apache.org/viewvc/jackrabbit/oak/trunk/oak-core/src/test/java/org/apache/jackrabbit/oak/cache/CacheTest.java?rev=1556209&view=auto
==============================================================================
---
jackrabbit/oak/trunk/oak-core/src/test/java/org/apache/jackrabbit/oak/cache/CacheTest.java
(added)
+++
jackrabbit/oak/trunk/oak-core/src/test/java/org/apache/jackrabbit/oak/cache/CacheTest.java
Tue Jan 7 13:25:40 2014
@@ -0,0 +1,606 @@
+/*
+ * 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.oak.cache;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertNull;
+import static org.junit.Assert.assertTrue;
+import static org.junit.Assert.fail;
+
+import java.util.HashMap;
+import java.util.HashSet;
+import java.util.List;
+import java.util.Map.Entry;
+import java.util.Random;
+import java.util.concurrent.ConcurrentMap;
+
+import org.junit.Test;
+
+/**
+ * Tests the LIRS cache.
+ */
+public class CacheTest {
+
+ @Test
+ public void testRandomSmallCache() {
+ Random r = new Random(1);
+ for (int i = 0; i < 10000; i++) {
+ int j = 0;
+ StringBuilder buff = new StringBuilder();
+ CacheLIRS<Integer, Integer> test = createCache(1 + r.nextInt(10));
+ for (; j < 30; j++) {
+ int key = r.nextInt(5);
+ switch (r.nextInt(3)) {
+ case 0:
+ int memory = r.nextInt(5) + 1;
+ buff.append("add ").append(key).append('
').append(memory).append('\n');
+ test.put(key, j, memory);
+ break;
+ case 1:
+ buff.append("remove ").append(key).append('\n');
+ test.invalidate(key);
+ break;
+ case 2:
+ buff.append("get ").append(key).append('\n');
+ test.getIfPresent(key);
+ }
+ }
+ }
+ }
+
+ @Test
+ public void testEdgeCases() {
+ CacheLIRS<Integer, Integer> test = createCache(1);
+ test.put(1, 10, 100);
+ assertEquals(10, test.getUnchecked(1).intValue());
+ try {
+ test.put(null, 10, 100);
+ fail();
+ } catch (NullPointerException e) {
+ // expected
+ }
+ try {
+ test.put(1, null, 100);
+ fail();
+ } catch (NullPointerException e) {
+ // expected
+ }
+ try {
+ test.setMaxMemory(0);
+ fail();
+ } catch (IllegalArgumentException e) {
+ // expected
+ }
+ try {
+ test.setAverageMemory(0);
+ fail();
+ } catch (IllegalArgumentException e) {
+ // expected
+ }
+ }
+
+ @Test
+ public void testSize() {
+ verifyMapSize(7, 16);
+ verifyMapSize(13, 32);
+ verifyMapSize(25, 64);
+ verifyMapSize(49, 128);
+ verifyMapSize(97, 256);
+ verifyMapSize(193, 512);
+ verifyMapSize(385, 1024);
+ verifyMapSize(769, 2048);
+
+ CacheLIRS<Integer, Integer> test;
+ test = createCache(3, 10);
+ test.put(0, 0, 9);
+ test.put(1, 10, 9);
+ test.put(2, 20, 9);
+ test.put(3, 30, 9);
+ test.put(4, 40, 9);
+
+ test = createCache(1, 1);
+ test.put(1, 10);
+ test.put(0, 0);
+ test.getUnchecked(0);
+
+ test = createCache(1000);
+ for (int j = 0; j < 2000; j++) {
+ test.put(j, j);
+ }
+ // for a cache of size 1000,
+ // there are 62 cold entries (about 6.25%).
+ assertEquals(62, test.size() - test.sizeHot());
+ // at most as many non-resident elements
+ // as there are entries in the stack
+ assertEquals(968, test.sizeNonResident());
+ }
+
+ private static void verifyMapSize(int elements, int expectedMapSize) {
+ CacheLIRS<Integer, Integer> test;
+ test = createCache(elements - 1);
+ assertTrue(test.sizeMapArray() < expectedMapSize);
+ test = createCache(elements);
+ assertEquals(expectedMapSize, test.sizeMapArray());
+ test = createCache(elements * 100, 100);
+ assertEquals(expectedMapSize, test.sizeMapArray());
+ }
+
+ @Test
+ public void testGetPutPeekRemove() {
+ CacheLIRS<Integer, Integer> test = createCache(4);
+ test.put(1, 10);
+ test.put(2, 20);
+ test.put(3, 30);
+ assertNull(test.peek(4));
+ assertNull(test.getIfPresent(4));
+ test.put(4, 40);
+ verify(test, "mem: 4 stack: 4 3 2 1 cold: non-resident:");
+ // move middle to front
+ assertEquals(30, test.getUnchecked(3).intValue());
+ assertEquals(20, test.getUnchecked(2).intValue());
+ assertEquals(20, test.peek(2).intValue());
+ // already on (an optimization)
+ assertEquals(20, test.getUnchecked(2).intValue());
+ assertEquals(10, test.peek(1).intValue());
+ assertEquals(10, test.getUnchecked(1).intValue());
+ verify(test, "mem: 4 stack: 1 2 3 4 cold: non-resident:");
+ test.put(3, 30);
+ verify(test, "mem: 4 stack: 3 1 2 4 cold: non-resident:");
+ // 5 is cold; will make 4 non-resident
+ test.put(5, 50);
+ verify(test, "mem: 4 stack: 5 3 1 2 cold: 5 non-resident: 4");
+ assertEquals(1, test.getMemory(1));
+ assertEquals(1, test.getMemory(5));
+ assertEquals(0, test.getMemory(4));
+ assertEquals(0, test.getMemory(100));
+ assertNull(test.peek(4));
+ assertNull(test.getIfPresent(4));
+ assertEquals(10, test.getUnchecked(1).intValue());
+ assertEquals(20, test.getUnchecked(2).intValue());
+ assertEquals(30, test.getUnchecked(3).intValue());
+ verify(test, "mem: 4 stack: 3 2 1 cold: 5 non-resident: 4");
+ assertEquals(50, test.getUnchecked(5).intValue());
+ verify(test, "mem: 4 stack: 5 3 2 1 cold: 5 non-resident: 4");
+ assertEquals(50, test.getUnchecked(5).intValue());
+ verify(test, "mem: 4 stack: 5 3 2 cold: 1 non-resident: 4");
+
+ // remove
+ assertEquals(50, test.remove(5).intValue());
+ assertNull(test.remove(5));
+ verify(test, "mem: 3 stack: 3 2 1 cold: non-resident: 4");
+ assertNull(test.remove(4));
+ verify(test, "mem: 3 stack: 3 2 1 cold: non-resident:");
+ assertNull(test.remove(4));
+ verify(test, "mem: 3 stack: 3 2 1 cold: non-resident:");
+ test.put(4, 40);
+ test.put(5, 50);
+ verify(test, "mem: 4 stack: 5 4 3 2 cold: 5 non-resident: 1");
+ test.getUnchecked(5);
+ test.getUnchecked(2);
+ test.getUnchecked(3);
+ test.getUnchecked(4);
+ verify(test, "mem: 4 stack: 4 3 2 5 cold: 2 non-resident: 1");
+ assertEquals(50, test.remove(5).intValue());
+ verify(test, "mem: 3 stack: 4 3 2 cold: non-resident: 1");
+ assertEquals(20, test.remove(2).intValue());
+ assertFalse(test.containsKey(1));
+ assertNull(test.remove(1));
+ assertFalse(test.containsKey(1));
+ verify(test, "mem: 2 stack: 4 3 cold: non-resident:");
+ test.put(1, 10);
+ test.put(2, 20);
+ verify(test, "mem: 4 stack: 2 1 4 3 cold: non-resident:");
+ test.getUnchecked(1);
+ test.getUnchecked(3);
+ test.getUnchecked(4);
+ verify(test, "mem: 4 stack: 4 3 1 2 cold: non-resident:");
+ assertEquals(10, test.remove(1).intValue());
+ verify(test, "mem: 3 stack: 4 3 2 cold: non-resident:");
+ test.remove(2);
+ test.remove(3);
+ test.remove(4);
+
+ // test clear
+ test.clear();
+ verify(test, "mem: 0 stack: cold: non-resident:");
+
+ // strange situation where there is only a non-resident entry
+ test.put(1, 10);
+ test.put(2, 20);
+ test.put(3, 30);
+ test.put(4, 40);
+ test.put(5, 50);
+ assertTrue(test.containsValue(50));
+ verify(test, "mem: 4 stack: 5 4 3 2 cold: 5 non-resident: 1");
+ test.put(1, 10);
+ verify(test, "mem: 4 stack: 1 5 4 3 2 cold: 1 non-resident: 5");
+ assertFalse(test.containsValue(50));
+ test.remove(2);
+ test.remove(3);
+ test.remove(4);
+ verify(test, "mem: 1 stack: 1 cold: non-resident: 5");
+ assertTrue(test.containsKey(1));
+ test.remove(1);
+ assertFalse(test.containsKey(1));
+ verify(test, "mem: 0 stack: cold: non-resident: 5");
+ assertFalse(test.containsKey(5));
+ assertTrue(test.isEmpty());
+
+ // verify that converting a hot to cold entry will prune the stack
+ test.clear();
+ test.put(1, 10);
+ test.put(2, 20);
+ test.put(3, 30);
+ test.put(4, 40);
+ test.put(5, 50);
+ test.getUnchecked(4);
+ test.getUnchecked(3);
+ verify(test, "mem: 4 stack: 3 4 5 2 cold: 5 non-resident: 1");
+ test.put(6, 60);
+ verify(test, "mem: 4 stack: 6 3 4 5 2 cold: 6 non-resident: 5 1");
+ // this will prune the stack (remove entry 5 as entry 2 becomes cold)
+ test.getUnchecked(6);
+ verify(test, "mem: 4 stack: 6 3 4 cold: 2 non-resident: 5 1");
+ }
+
+ @Test
+ public void testPruneStack() {
+ CacheLIRS<Integer, Integer> test = createCache(5);
+ for (int i = 0; i < 7; i++) {
+ test.put(i, i * 10);
+ }
+ verify(test, "mem: 5 stack: 6 5 4 3 2 1 cold: 6 non-resident: 5 0");
+ test.getUnchecked(4);
+ test.getUnchecked(3);
+ test.getUnchecked(2);
+ verify(test, "mem: 5 stack: 2 3 4 6 5 1 cold: 6 non-resident: 5 0");
+ // this call needs to prune the stack
+ test.remove(1);
+ verify(test, "mem: 4 stack: 2 3 4 6 cold: non-resident: 5 0");
+ test.put(0, 0);
+ test.put(1, 10);
+ // the the stack was not pruned, the following will fail
+ verify(test, "mem: 5 stack: 1 0 2 3 4 cold: 1 non-resident: 6 5");
+ }
+
+ @Test
+ public void testClear() {
+ CacheLIRS<Integer, Integer> test = createCache(40, 10);
+ for (int i = 0; i < 5; i++) {
+ test.put(i, 10 * i, 9);
+ }
+ verify(test, "mem: 36 stack: 4 3 2 1 cold: 4 non-resident: 0");
+ for (Entry<Integer, Integer> e : test.entrySet()) {
+ assertTrue(e.getKey() >= 1 && e.getKey() <= 4);
+ assertTrue(e.getValue() >= 10 && e.getValue() <= 40);
+ }
+ for (int x : test.asMap().values()) {
+ assertTrue(x >= 10 && x <= 40);
+ }
+ for (int x : test.keySet()) {
+ assertTrue(x >= 1 && x <= 4);
+ }
+ assertEquals(40, test.getMaxMemory());
+ assertEquals(10, test.getAverageMemory());
+ assertEquals(36, test.getUsedMemory());
+ assertEquals(4, test.size());
+ assertEquals(3, test.sizeHot());
+ assertEquals(1, test.sizeNonResident());
+ assertFalse(test.isEmpty());
+
+ // changing the limit is not supposed to modify the map
+ test.setMaxMemory(10);
+ assertEquals(10, test.getMaxMemory());
+ test.setMaxMemory(40);
+ test.setAverageMemory(1);
+ assertEquals(1, test.getAverageMemory());
+ test.setAverageMemory(10);
+ verify(test, "mem: 36 stack: 4 3 2 1 cold: 4 non-resident: 0");
+
+ // putAll uses the average memory
+ test.asMap().putAll(test.asMap());
+ verify(test, "mem: 40 stack: 4 3 2 1 cold: non-resident: 0");
+
+ test.clear();
+ verify(test, "mem: 0 stack: cold: non-resident:");
+
+ assertEquals(40, test.getMaxMemory());
+ assertEquals(10, test.getAverageMemory());
+ assertEquals(0, test.getUsedMemory());
+ assertEquals(0, test.size());
+ assertEquals(0, test.sizeHot());
+ assertEquals(0, test.sizeNonResident());
+ assertTrue(test.isEmpty());
+ }
+
+ @Test
+ public void testLimitHot() {
+ CacheLIRS<Integer, Integer> test = createCache(100);
+ for (int i = 0; i < 300; i++) {
+ test.put(i, 10 * i);
+ }
+ assertEquals(100, test.size());
+ assertEquals(99, test.sizeNonResident());
+ assertEquals(93, test.sizeHot());
+ }
+
+ @Test
+ public void testLimitNonResident() {
+ CacheLIRS<Integer, Integer> test = createCache(4);
+ for (int i = 0; i < 20; i++) {
+ test.put(i, 10 * i);
+ }
+ verify(test, "mem: 4 stack: 19 18 17 16 3 2 1 cold: 19 non-resident:
18 17 16");
+ }
+
+ @Test
+ public void testBadHashMethod() {
+ // ensure an 2^n cache size
+ final int size = 4;
+
+ /**
+ * A class with a bad hashCode implementation.
+ */
+ class BadHash {
+ int x;
+
+ BadHash(int x) {
+ this.x = x;
+ }
+
+ @Override
+ public int hashCode() {
+ return (x & 1) * size * 2;
+ }
+
+ @Override
+ public boolean equals(Object o) {
+ return ((BadHash) o).x == x;
+ }
+
+ @Override
+ public String toString() {
+ return "" + x;
+ }
+
+ }
+
+ CacheLIRS<BadHash, Integer> test = createCache(size * 2);
+ for (int i = 0; i < size; i++) {
+ test.put(new BadHash(i), i);
+ }
+ for (int i = 0; i < size; i++) {
+ if (i % 3 == 0) {
+ assertEquals(i, test.remove(new BadHash(i)).intValue());
+ assertNull(test.remove(new BadHash(i)));
+ }
+ }
+ for (int i = 0; i < size; i++) {
+ if (i % 3 == 0) {
+ assertNull(test.getIfPresent(new BadHash(i)));
+ } else {
+ assertEquals(i, test.getIfPresent(new BadHash(i)).intValue());
+ }
+ }
+ for (int i = 0; i < size; i++) {
+ test.put(new BadHash(i), i);
+ }
+ for (int i = 0; i < size; i++) {
+ if (i % 3 == 0) {
+ assertEquals(i, test.remove(new BadHash(i)).intValue());
+ assertNull(test.remove(new BadHash(i)));
+ }
+ }
+ for (int i = 0; i < size; i++) {
+ if (i % 3 == 0) {
+ assertNull(test.getIfPresent(new BadHash(i)));
+ } else {
+ assertEquals(i, test.getIfPresent(new BadHash(i)).intValue());
+ }
+ }
+ }
+
+ @Test
+ public void testScanResistance() {
+ boolean log = false;
+ int size = 20;
+ // cache size 11 (10 hot, 1 cold)
+ CacheLIRS<Integer, Integer> test = createCache(size / 2 + 1);
+ // init the cache with some dummy entries
+ for (int i = 0; i < size; i++) {
+ test.put(-i, -i * 10);
+ }
+ verify(test, null);
+ // init with 0..9, ensure those are hot entries
+ for (int i = 0; i < size / 2; i++) {
+ test.put(i, i * 10);
+ test.getUnchecked(i);
+ if (log) {
+ System.out.println("get " + i + " -> " + test);
+ }
+ }
+ verify(test, null);
+ // read 0..9, add 10..19 (cold)
+ for (int i = 0; i < size; i++) {
+ Integer x = test.getIfPresent(i);
+ Integer y = test.peek(i);
+ if (i < size / 2) {
+ assertTrue("i: " + i, x != null);
+ assertTrue("i: " + i, y != null);
+ assertEquals(i * 10, x.intValue());
+ assertEquals(i * 10, y.intValue());
+ } else {
+ assertNull(x);
+ assertNull(y);
+ test.put(i, i * 10);
+ // peek should have no effect
+ assertEquals(i * 10, test.peek(i).intValue());
+ }
+ if (log) {
+ System.out.println("get " + i + " -> " + test);
+ }
+ verify(test, null);
+ }
+ // ensure 0..9 are hot, 10..18 are not resident, 19 is cold
+ for (int i = 0; i < size; i++) {
+ Integer x = test.getIfPresent(i);
+ if (i < size / 2 || i == size - 1) {
+ assertTrue("i: " + i, x != null);
+ assertEquals(i * 10, x.intValue());
+ } else {
+ assertNull(x);
+ }
+ verify(test, null);
+ }
+ }
+
+ @Test
+ public void testRandomOperations() {
+ boolean log = false;
+ int size = 10;
+ Random r = new Random(1);
+ for (int j = 0; j < 100; j++) {
+ CacheLIRS<Integer, Integer> test = createCache(size / 2);
+ HashMap<Integer, Integer> good = new HashMap<Integer, Integer>();
+ for (int i = 0; i < 10000; i++) {
+ int key = r.nextInt(size);
+ int value = r.nextInt();
+ switch (r.nextInt(3)) {
+ case 0:
+ if (log) {
+ System.out.println(i + " put " + key + " " + value);
+ }
+ good.put(key, value);
+ test.put(key, value);
+ break;
+ case 1:
+ if (log) {
+ System.out.println(i + " get " + key);
+ }
+ Integer a = good.get(key);
+ Integer b = test.getIfPresent(key);
+ if (a == null) {
+ assertNull(b);
+ } else if (b != null) {
+ assertEquals(a, b);
+ }
+ break;
+ case 2:
+ if (log) {
+ System.out.println(i + " remove " + key);
+ }
+ good.remove(key);
+ test.remove(key);
+ break;
+ }
+ if (log) {
+ System.out.println(" -> " + toString(test));
+ }
+ }
+ verify(test, null);
+ }
+ }
+
+ private static <K, V> String toString(CacheLIRS<K, V> cache) {
+ StringBuilder buff = new StringBuilder();
+ buff.append("mem: " + cache.getUsedMemory());
+ buff.append(" stack:");
+ for (K k : cache.keys(false, false)) {
+ buff.append(' ').append(k);
+ }
+ buff.append(" cold:");
+ for (K k : cache.keys(true, false)) {
+ buff.append(' ').append(k);
+ }
+ buff.append(" non-resident:");
+ for (K k : cache.keys(true, true)) {
+ buff.append(' ').append(k);
+ }
+ return buff.toString();
+ }
+
+ private static <K, V> void verify(CacheLIRS<K, V> cache, String expected) {
+ if (expected != null) {
+ String got = toString(cache);
+ assertEquals(expected, got);
+ }
+ int mem = 0;
+ for (K k : cache.keySet()) {
+ mem += cache.getMemory(k);
+ }
+ assertEquals(mem, cache.getUsedMemory());
+ List<K> stack = cache.keys(false, false);
+ List<K> cold = cache.keys(true, false);
+ List<K> nonResident = cache.keys(true, true);
+ assertEquals(nonResident.size(), cache.sizeNonResident());
+ HashSet<K> hot = new HashSet<K>(stack);
+ hot.removeAll(cold);
+ hot.removeAll(nonResident);
+ assertEquals(hot.size(), cache.sizeHot());
+ assertEquals(hot.size() + cold.size(), cache.size());
+ if (stack.size() > 0) {
+ K lastStack = stack.get(stack.size() - 1);
+ assertTrue(hot.contains(lastStack));
+ }
+ }
+
+ private static <K, V> CacheLIRS<K, V> createCache(int maxElements) {
+ return createCache(maxElements, 1);
+ }
+
+ private static <K, V> CacheLIRS<K, V> createCache(int maxSize, int
averageSize) {
+ return new CacheLIRS<K, V>(null, maxSize, averageSize, 1, 0, null);
+ }
+
+ @Test
+ public void testAsMap() {
+ CacheLIRS<Integer, String> cache = createCache(10, 1);
+ ConcurrentMap<Integer, String> map = cache.asMap();
+ assertNull(map.putIfAbsent(1, "Hello"));
+ assertEquals(1, map.size());
+ assertEquals("Hello", cache.getIfPresent(1));
+ assertEquals("Hello", map.putIfAbsent(1, "Hallo"));
+ assertEquals("Hello", cache.getIfPresent(1));
+ assertEquals("Hello", map.replace(1, "Hallo"));
+ assertEquals("Hallo", cache.getIfPresent(1));
+ assertFalse(map.replace(1, "x", "y"));
+ assertTrue(map.replace(1, "Hallo", "Hi!"));
+ assertFalse(map.remove(1, "Hello"));
+ assertTrue(map.remove(1, "Hi!"));
+ assertEquals(0, map.size());
+
+ map.put(1, "Hello");
+ assertEquals("[1]", map.keySet().toString());
+ assertEquals("[1=Hello]", cache.entrySet().toString());
+ assertEquals("Hello", map.get(1));
+ assertTrue(map.containsKey(1));
+ assertFalse(map.containsKey(2));
+ assertTrue(map.containsValue("Hello"));
+ assertFalse(map.containsValue("Hallo"));
+ assertFalse(map.isEmpty());
+ map.remove(1);
+ assertTrue(map.isEmpty());
+
+ map.put(1, "Hello");
+ map.clear();
+ assertTrue(map.isEmpty());
+ }
+
+}
Modified:
jackrabbit/oak/trunk/oak-core/src/test/java/org/apache/jackrabbit/oak/kernel/KernelNodeStoreCacheTest.java
URL:
http://svn.apache.org/viewvc/jackrabbit/oak/trunk/oak-core/src/test/java/org/apache/jackrabbit/oak/kernel/KernelNodeStoreCacheTest.java?rev=1556209&r1=1556208&r2=1556209&view=diff
==============================================================================
---
jackrabbit/oak/trunk/oak-core/src/test/java/org/apache/jackrabbit/oak/kernel/KernelNodeStoreCacheTest.java
(original)
+++
jackrabbit/oak/trunk/oak-core/src/test/java/org/apache/jackrabbit/oak/kernel/KernelNodeStoreCacheTest.java
Tue Jan 7 13:25:40 2014
@@ -71,7 +71,8 @@ public class KernelNodeStoreCacheTest {
int uncachedReads = readTreeWithCleanedCache();
modifyContent();
int cachedReads = readTreeWithCache();
- assertTrue(cachedReads < uncachedReads);
+ assertTrue("cachedReads: " + cachedReads + " uncachedReads: " +
uncachedReads,
+ cachedReads < uncachedReads);
}
/**
@@ -84,7 +85,8 @@ public class KernelNodeStoreCacheTest {
int uncachedReads = readTreeWithCleanedCache();
modifyContent();
int cachedReads = readTreeWithCache();
- assertEquals(cachedReads, uncachedReads);
+ assertEquals("cachedReads: " + cachedReads + " uncachedReads: " +
uncachedReads,
+ cachedReads, uncachedReads);
}
/**
@@ -96,7 +98,8 @@ public class KernelNodeStoreCacheTest {
int uncachedReads = readTreeWithCleanedCache();
modifyContent();
int cachedReads = readTreeWithCache();
- assertTrue(cachedReads < uncachedReads);
+ assertTrue("cachedReads: " + cachedReads + " uncachedReads: " +
uncachedReads,
+ cachedReads < uncachedReads);
}
/**
@@ -113,7 +116,8 @@ public class KernelNodeStoreCacheTest {
int cachedReads = readTreeWithCache();
// System.out.println("Cached reads: " + cachedReads);
- assertTrue(cachedReads < uncachedReads);
+ assertTrue("cachedReads: " + cachedReads + " uncachedReads: " +
uncachedReads,
+ cachedReads < uncachedReads);
}
//---------------------------< internal
>-----------------------------------