diff --git a/oak-core/src/main/java/org/apache/jackrabbit/oak/cache/CacheLIRS.java b/oak-core/src/main/java/org/apache/jackrabbit/oak/cache/CacheLIRS.java index 7e21088..c8d1258 100644 --- a/oak-core/src/main/java/org/apache/jackrabbit/oak/cache/CacheLIRS.java +++ b/oak-core/src/main/java/org/apache/jackrabbit/oak/cache/CacheLIRS.java @@ -37,6 +37,7 @@ 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.RemovalCause; import com.google.common.cache.Weigher; import com.google.common.collect.ImmutableMap; import com.google.common.util.concurrent.ListenableFuture; @@ -96,8 +97,9 @@ public class CacheLIRS implements LoadingCache { * * @param key the evicted item's key * @param value the evicted item's value or {@code null} if non-resident + * @param cause the cause of the eviction */ - void evicted(@Nonnull K key, @Nullable V value); + void evicted(@Nonnull K key, @Nullable V value, @Nonnull RemovalCause cause); } /** @@ -202,17 +204,17 @@ public class CacheLIRS implements LoadingCache { Segment old = segments[index]; segments[index] = s; if (evicted != null && old != null && old != s) { - old.evictedAll(); + old.evictedAll(RemovalCause.EXPLICIT); } } - void evicted(Entry entry) { + void evicted(Entry entry, RemovalCause cause) { if (evicted == null) { return; } K key = entry.key; if (key != null) { - evicted.evicted(key, entry.value); + evicted.evicted(key, entry.value, cause); } } @@ -384,7 +386,7 @@ public class CacheLIRS implements LoadingCache { @Override public void invalidate(Object key) { int hash = getHash(key); - getSegment(hash).invalidate(key, hash); + getSegment(hash).invalidate(key, hash, RemovalCause.EXPLICIT); } /** @@ -618,7 +620,7 @@ public class CacheLIRS implements LoadingCache { for (Segment s : segments) { synchronized (s) { if (evicted != null) { - s.evictedAll(); + s.evictedAll(RemovalCause.EXPLICIT); } s.clear(); } @@ -775,19 +777,19 @@ public class CacheLIRS implements LoadingCache { clear(); } - public void evictedAll() { + public void evictedAll(RemovalCause cause) { for (Entry e = stack.stackNext; e != stack; e = e.stackNext) { if (e.value != null) { - cache.evicted(e); + cache.evicted(e, cause); } } for (Entry e = queue.queueNext; e != queue; e = e.queueNext) { if (e.stackNext == null) { - cache.evicted(e); + cache.evicted(e, cause); } } for (Entry e = queue2.queueNext; e != queue2; e = e.queueNext) { - cache.evicted(e); + cache.evicted(e, cause); } } @@ -1043,7 +1045,7 @@ public class CacheLIRS implements LoadingCache { synchronized boolean remove(Object key, int hash, Object value) { V old = get(key, hash); if (old != null && old.equals(value)) { - invalidate(key, hash); + invalidate(key, hash, RemovalCause.EXPLICIT); return true; } return false; @@ -1052,7 +1054,7 @@ public class CacheLIRS implements LoadingCache { 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); + invalidate(key, hash, RemovalCause.EXPLICIT); return old; } @@ -1112,7 +1114,7 @@ public class CacheLIRS implements LoadingCache { old = null; } else { old = e.value; - invalidate(key, hash); + invalidate(key, hash, RemovalCause.REPLACED); } e = new Entry(); e.key = key; @@ -1141,7 +1143,7 @@ public class CacheLIRS implements LoadingCache { * @param key the key (may not be null) * @param hash the hash */ - synchronized void invalidate(Object key, int hash) { + synchronized void invalidate(Object key, int hash, RemovalCause cause) { Entry[] array = entries; int mask = array.length - 1; int index = hash & mask; @@ -1181,7 +1183,7 @@ public class CacheLIRS implements LoadingCache { removeFromQueue(e); } pruneStack(); - cache.evicted(e); + cache.evicted(e, cause); } /** @@ -1209,7 +1211,7 @@ public class CacheLIRS implements LoadingCache { usedMemory -= e.memory; evictionCount++; removeFromQueue(e); - cache.evicted(e); + cache.evicted(e, RemovalCause.SIZE); e.value = null; e.memory = 0; addToQueue(queue2, e); @@ -1217,7 +1219,7 @@ public class CacheLIRS implements LoadingCache { while (queue2Size + queue2Size > stackSize) { e = queue2.queuePrev; int hash = getHash(e.key); - invalidate(e.key, hash); + invalidate(e.key, hash, RemovalCause.SIZE); } } } diff --git a/oak-core/src/test/java/org/apache/jackrabbit/oak/cache/CacheTest.java b/oak-core/src/test/java/org/apache/jackrabbit/oak/cache/CacheTest.java index ba0c90a..a3fa08b 100644 --- a/oak-core/src/test/java/org/apache/jackrabbit/oak/cache/CacheTest.java +++ b/oak-core/src/test/java/org/apache/jackrabbit/oak/cache/CacheTest.java @@ -26,9 +26,11 @@ import static org.junit.Assert.assertNull; import static org.junit.Assert.assertTrue; import static org.junit.Assert.fail; +import java.util.Arrays; import java.util.HashMap; import java.util.HashSet; import java.util.List; +import java.util.Map; import java.util.Map.Entry; import java.util.Random; import java.util.Set; @@ -40,6 +42,7 @@ import org.apache.jackrabbit.oak.cache.CacheLIRS.EvictionCallback; import org.junit.Test; import com.google.common.cache.CacheLoader; +import com.google.common.cache.RemovalCause; import com.google.common.cache.Weigher; import com.google.common.util.concurrent.ListenableFuture; import com.google.common.util.concurrent.SettableFuture; @@ -693,12 +696,13 @@ public class CacheTest { .maximumSize(100) .evictionCallback(new EvictionCallback() { @Override - public void evicted(String key, Integer value) { + public void evicted(String key, Integer value, RemovalCause cause) { evictedKeys.add(key); if (value != null) { assertEquals(key, valueOf(value)); evictedValues.add(value); } + assertTrue(cause == RemovalCause.SIZE || cause == RemovalCause.EXPLICIT); } }) .build(); @@ -718,6 +722,69 @@ public class CacheTest { } @Test + public void evictionCallbackCause() { + final Map causes = new HashMap(); + + CacheLIRS cache = CacheLIRS.newBuilder().maximumSize(100) + .evictionCallback(new EvictionCallback() { + @Override + public void evicted(String key, Integer value, RemovalCause cause) { + if (key.startsWith("ignore-")) { + return; + } + causes.put(key, cause); + } + }).build(); + + cache.put("k1", 1); + cache.remove("k1"); + assertEquals(RemovalCause.EXPLICIT, causes.remove("k1")); + + cache.put("k6", 1); + cache.remove("k6", 1); + assertEquals(RemovalCause.EXPLICIT, causes.remove("k6")); + + cache.put("k2", 1); + cache.invalidate("k2"); + assertEquals(RemovalCause.EXPLICIT, causes.remove("k2")); + + cache.put("k3", 1); + cache.invalidateAll(); + assertEquals(RemovalCause.EXPLICIT, causes.remove("k3")); + + cache.put("k4", 1); + cache.put("k5", 1); + cache.invalidateAll(Arrays.asList("k4", "k5")); + assertEquals(RemovalCause.EXPLICIT, causes.remove("k4")); + assertEquals(RemovalCause.EXPLICIT, causes.remove("k5")); + + cache.put("k7", 1); + cache.clear(); + assertEquals(RemovalCause.EXPLICIT, causes.remove("k7")); + + cache.put("k8", 1); + cache.put("k8", 2); + assertEquals(RemovalCause.REPLACED, causes.remove("k8")); + + for (int i = 0; i < 50; i++) { + cache.put("kk" + i, 1); + } + for (int i = 0; i < 200; i++) { + cache.put("ignore-" + i, Integer.MAX_VALUE); + } + + int checkedCount = 0; + for (int i = 0; i < 50; i++) { + String key = "kk" + i; + if (!cache.containsKey(key)) { + assertEquals("Callback hasn't been called for " + key, RemovalCause.SIZE, causes.get(key)); + checkedCount++; + } + } + assertTrue(checkedCount > 10); + } + + @Test public void evictionCallbackRandomized() throws ExecutionException { final HashMap evictedMap = new HashMap(); final HashSet evictedNonResidentSet = new HashSet(); @@ -725,7 +792,7 @@ public class CacheTest { .maximumSize(10) .evictionCallback(new EvictionCallback() { @Override - public void evicted(Integer key, Integer value) { + public void evicted(Integer key, Integer value, RemovalCause cause) { if (value == null) { assertTrue(evictedNonResidentSet.add(key)); } else {