add maxSize parameter to HotEntryCache

This commit is contained in:
2019-08-24 19:24:20 +02:00
parent 00c20dae6b
commit 6eaf4e10fc
5 changed files with 168 additions and 44 deletions

View File

@@ -2,9 +2,12 @@ package org.lucares.utils.cache;
import java.time.Duration;
import java.time.Instant;
import java.time.temporal.ChronoUnit;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.LinkedHashSet;
import java.util.List;
import java.util.Set;
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.ExecutionException;
@@ -15,6 +18,8 @@ import java.util.concurrent.TimeoutException;
import org.apache.logging.log4j.Level;
import org.apache.logging.log4j.core.config.Configurator;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.testng.Assert;
import org.testng.annotations.Test;
@@ -25,11 +30,17 @@ public class HotEntryCacheTest {
Configurator.setRootLevel(Level.TRACE);
}
private static final Logger LOGGER = LoggerFactory.getLogger(HotEntryCacheTest.class);
private int cacheId = 0;
@Test(invocationCount = 1)
public void testRemovalListenerCalledOnExpire() throws InterruptedException {
LOGGER.info("");
LOGGER.info("");
LOGGER.info("start: testRemovalListenerCalledOnExpire");
final long originalMinSleepPeriod = HotEntryCache.getMinSleepPeriod();
final long originalMaxSleepPeriod = HotEntryCache.getMaxSleepPeriod();
try {
@@ -37,9 +48,10 @@ public class HotEntryCacheTest {
final String value = "value";
final CountDownLatch latch = new CountDownLatch(1);
final HotEntryCache<String, String> cache = new HotEntryCache<>(Duration.ofMillis(1), "cache-" + ++cacheId);
final HotEntryCache<String, String> cache = new HotEntryCache<>(Duration.ofMillis(1), 10,
"cache-" + ++cacheId);
HotEntryCache.setMinSleepPeriod(1);
HotEntryCache.setMaxSleepPeriod(10);
HotEntryCache.setMaxSleepPeriod(2);
cache.addListener((k, v) -> {
Assert.assertEquals(k, key);
Assert.assertEquals(v, value);
@@ -56,7 +68,7 @@ public class HotEntryCacheTest {
}
public void testPutAndGet() throws InterruptedException, ExecutionException, TimeoutException {
final HotEntryCache<String, String> cache = new HotEntryCache<>(Duration.ofSeconds(10));
final HotEntryCache<String, String> cache = new HotEntryCache<>(Duration.ofSeconds(10), 10);
final String replacedNull = cache.put("key", "value1");
Assert.assertEquals(replacedNull, null);
@@ -74,7 +86,7 @@ public class HotEntryCacheTest {
public void testPutTouches() throws InterruptedException, ExecutionException, TimeoutException {
final ModifiableFixedTimeClock clock = new ModifiableFixedTimeClock();
final Duration timeToLive = Duration.ofSeconds(10);
final HotEntryCache<String, String> cache = new HotEntryCache<>(timeToLive, clock);
final HotEntryCache<String, String> cache = new HotEntryCache<>(timeToLive, 10, clock);
cache.put("key", "value1");
@@ -102,7 +114,7 @@ public class HotEntryCacheTest {
public void testGetTouches() throws Exception {
final ModifiableFixedTimeClock clock = new ModifiableFixedTimeClock();
final Duration timeToLive = Duration.ofSeconds(10);
final HotEntryCache<String, String> cache = new HotEntryCache<>(timeToLive, clock);
final HotEntryCache<String, String> cache = new HotEntryCache<>(timeToLive, 10, clock);
cache.put("key", "value1");
@@ -121,7 +133,7 @@ public class HotEntryCacheTest {
public void testEvictionByBackgroundThread() throws InterruptedException, ExecutionException, TimeoutException {
final ModifiableFixedTimeClock clock = new ModifiableFixedTimeClock();
final Duration timeToLive = Duration.ofSeconds(10);
final HotEntryCache<String, String> cache = new HotEntryCache<>(timeToLive, clock);
final HotEntryCache<String, String> cache = new HotEntryCache<>(timeToLive, 10, clock);
final CompletableFuture<String> evictionEventFuture = new CompletableFuture<>();
cache.addListener((key, value) -> {
@@ -142,7 +154,11 @@ public class HotEntryCacheTest {
}
public void testRemove() throws InterruptedException, ExecutionException, TimeoutException {
final HotEntryCache<String, String> cache = new HotEntryCache<>(Duration.ofSeconds(10));
LOGGER.info("");
LOGGER.info("");
LOGGER.info("start: testRemove");
final HotEntryCache<String, String> cache = new HotEntryCache<>(Duration.ofSeconds(10), 10);
final List<String> removedValues = new ArrayList<>();
cache.addListener((key, value) -> removedValues.add(value));
@@ -158,7 +174,7 @@ public class HotEntryCacheTest {
}
public void testClear() throws InterruptedException, ExecutionException, TimeoutException {
final HotEntryCache<String, String> cache = new HotEntryCache<>(Duration.ofSeconds(10));
final HotEntryCache<String, String> cache = new HotEntryCache<>(Duration.ofSeconds(10), 10);
final List<String> removedValues = new ArrayList<>();
cache.addListener((key, value) -> removedValues.add(value));
@@ -177,7 +193,7 @@ public class HotEntryCacheTest {
public void testForEachTouches() throws InterruptedException, ExecutionException, TimeoutException {
final ModifiableFixedTimeClock clock = new ModifiableFixedTimeClock();
final Duration timeToLive = Duration.ofSeconds(10);
final HotEntryCache<String, String> cache = new HotEntryCache<>(timeToLive, clock);
final HotEntryCache<String, String> cache = new HotEntryCache<>(timeToLive, 10, clock);
final CompletableFuture<String> evictionEventFuture = new CompletableFuture<>();
cache.addListener((key, value) -> {
@@ -220,7 +236,7 @@ public class HotEntryCacheTest {
* @throws Exception
*/
public void testPutIfAbsentIsAtomic() throws Exception {
final HotEntryCache<String, String> cache = new HotEntryCache<>(Duration.ofSeconds(10));
final HotEntryCache<String, String> cache = new HotEntryCache<>(Duration.ofSeconds(10), 10);
final ExecutorService pool = Executors.newCachedThreadPool();
try {
@@ -255,7 +271,7 @@ public class HotEntryCacheTest {
}
public void testPutIfAbsentReturnsExistingValue() throws Exception {
final HotEntryCache<String, String> cache = new HotEntryCache<>(Duration.ofSeconds(10));
final HotEntryCache<String, String> cache = new HotEntryCache<>(Duration.ofSeconds(10), 10);
final String key = "key";
final String valueA = "A";
@@ -271,7 +287,7 @@ public class HotEntryCacheTest {
}
public void testPutIfAbsentDoesNotAddNull() throws Exception {
final HotEntryCache<String, String> cache = new HotEntryCache<>(Duration.ofSeconds(10));
final HotEntryCache<String, String> cache = new HotEntryCache<>(Duration.ofSeconds(10), 10);
final String key = "key";
final String returnedByPutIfAbsent = cache.putIfAbsent(key, k -> null);
@@ -281,6 +297,60 @@ public class HotEntryCacheTest {
Assert.assertEquals(actualInCache, null);
}
public void testMaxSizeIsRespected() {
final int maxSize = 10;
final ModifiableFixedTimeClock clock = new ModifiableFixedTimeClock();
final HotEntryCache<String, String> cache = new HotEntryCache<>(Duration.ofHours(1), maxSize, clock);
final Set<String> removedKeys = new LinkedHashSet<>();
cache.addListener((key, value) -> removedKeys.add(key));
// fill the cache
int count = 0;
for (count = 0; count < maxSize; count++) {
cache.put("key" + count, "value" + count);
clock.plus(2L, ChronoUnit.MILLIS);
cache.updateTime();
}
Assert.assertEquals(cache.size(), maxSize, "cache is full");
Assert.assertEquals(removedKeys, List.of(), "removed keys at point A");
// add an item to a full cache -> the oldest 20% of the entries will be evicted
// before the new entry is added
cache.put("key" + count, "value" + count);
clock.plus(2L, ChronoUnit.MILLIS);
cache.updateTime();
count++;
Assert.assertEquals(cache.size(), maxSize - 1, "cache was full, 20% (2 items) were removed and one added");
Assert.assertEquals(removedKeys, Set.of("key0", "key1"), "removed keys at point B");
}
public void testEvictionDueToSizeLimitDoesNotRemoveMoreThan20Percent() {
final int maxSize = 10;
final ModifiableFixedTimeClock clock = new ModifiableFixedTimeClock();
final HotEntryCache<String, String> cache = new HotEntryCache<>(Duration.ofHours(1), maxSize, clock);
final Set<String> removedKeys = new LinkedHashSet<>();
cache.addListener((key, value) -> removedKeys.add(key));
// fill the cache
int count = 0;
for (count = 0; count < maxSize; count++) {
// all entries get the same eviction time due to the fixed clock
cache.put("key" + count, "value" + count);
}
Assert.assertEquals(cache.size(), maxSize, "cache is full");
Assert.assertEquals(removedKeys, List.of(), "removed keys at point A");
// add an item to a full cache -> the oldest 20% of the entries will be evicted
// before the new entry is added
cache.put("key" + count, "value" + count);
count++;
Assert.assertEquals(cache.size(), maxSize - 1, "cache was full, 20% (2 items) were removed and one added");
Assert.assertEquals(removedKeys.size(), (int) (maxSize * 0.2), "number of removed keys at point B");
}
private void sleep(final TimeUnit timeUnit, final long timeout) {
try {
timeUnit.sleep(timeout);
@@ -302,7 +372,7 @@ public class HotEntryCacheTest {
Configurator.setRootLevel(Level.TRACE);
final Duration timeToLive = Duration.ofSeconds(1);
final HotEntryCache<String, String> cache = new HotEntryCache<>(timeToLive);
final HotEntryCache<String, String> cache = new HotEntryCache<>(timeToLive, 10);
cache.addListener((key, value) -> {
System.out.println(Instant.now() + " evicting: " + key + " -> " + value);