diff --git a/pdb-utils/src/main/java/org/lucares/utils/cache/HotEntryCache.java b/pdb-utils/src/main/java/org/lucares/utils/cache/HotEntryCache.java
index 67ce8bc..5559315 100644
--- a/pdb-utils/src/main/java/org/lucares/utils/cache/HotEntryCache.java
+++ b/pdb-utils/src/main/java/org/lucares/utils/cache/HotEntryCache.java
@@ -2,7 +2,6 @@ package org.lucares.utils.cache;
import java.time.Clock;
import java.time.Duration;
-import java.time.Instant;
import java.util.ConcurrentModificationException;
import java.util.HashSet;
import java.util.Set;
@@ -31,8 +30,16 @@ import org.slf4j.LoggerFactory;
* Caching frameworks like EhCache only evict entries when a new entry is added.
* That might not be desired, e.g. when the cached objects block resources.
*
- * This cache is a simple wrapper for a ConcurrentHashMap that evicts entries
- * after timeToLive+5s.
+ * This cache is a wrapper for a ConcurrentHashMap that evicts entries after the
+ * specified timeToLive. The cache spawns two threads.
+ *
+ * - One for updating time. It is far too expensive to call
+ * {@link System#currentTimeMillis()} or something similar whenever a timestamp
+ * is needed. Therefore we have a thread that updates a member periodically.
+ *
- One for evicting elements. So that elements can be evicted based on their
+ * time to live. Other cache implementations do this only when elements are
+ * added to the cache.
+ *
*/
public class HotEntryCache {
@@ -67,11 +74,11 @@ public class HotEntryCache {
}
private final static class Entry {
- private Instant lastAccessed;
+ private long lastAccessed;
private V value;
- public Entry(final V value, final Instant creationTime) {
+ public Entry(final V value, final long creationTime) {
this.value = value;
lastAccessed = creationTime;
}
@@ -84,11 +91,11 @@ public class HotEntryCache {
this.value = value;
}
- public Instant getLastAccessed() {
+ public long getLastAccessed() {
return lastAccessed;
}
- public void touch(final Instant instant) {
+ public void touch(final long instant) {
lastAccessed = instant;
}
}
@@ -110,8 +117,8 @@ public class HotEntryCache {
}
}
- public void setUpdateInterval(final Duration updateInterval) {
- this.updateInterval = Math.max(updateInterval.toMillis(), 1);
+ public void setUpdateInterval(final long updateInterval) {
+ this.updateInterval = Math.max(updateInterval, 1);
interrupt();
}
@@ -147,8 +154,8 @@ public class HotEntryCache {
private final AtomicReference> future = new AtomicReference<>(null);
private final Object lock = new Object();
- private Duration minSleepPeriod = Duration.ofSeconds(5);
- private Duration maxSleepPeriod = Duration.ofDays(1);
+ private long minSleepPeriodInMs = Duration.ofSeconds(5).toMillis();
+ private long maxSleepPeriodInMs = Duration.ofDays(1).toMillis();
public EvictionThread() {
setDaemon(true);
@@ -161,17 +168,17 @@ public class HotEntryCache {
}
}
- private Duration getMinSleepPeriod() {
- return minSleepPeriod;
+ private long getMinSleepPeriod() {
+ return minSleepPeriodInMs;
}
- private Duration getMaxSleepPeriod() {
- return maxSleepPeriod;
+ private long getMaxSleepPeriod() {
+ return maxSleepPeriodInMs;
}
@Override
public void run() {
- Duration timeToNextEviction = maxSleepPeriod;
+ long timeToNextEviction = maxSleepPeriodInMs;
while (true) {
sleepToNextEviction(timeToNextEviction);
@@ -179,7 +186,7 @@ public class HotEntryCache {
final CompletableFuture future = this.future.getAcquire();
try {
- final Instant minNextEvictionTime = evictStaleEntries();
+ final long minNextEvictionTime = evictStaleEntries();
timeToNextEviction = normalizeDurationToNextEviction(minNextEvictionTime);
@@ -194,34 +201,34 @@ public class HotEntryCache {
}
}
- private Duration normalizeDurationToNextEviction(final Instant minNextEvictionTime) {
- Duration timeToNextEviction;
- if (!minNextEvictionTime.equals(Instant.MAX)) {
- timeToNextEviction = minSleepPeriod;
+ private long normalizeDurationToNextEviction(final long minNextEvictionTime) {
+ long timeToNextEviction;
+ if (minNextEvictionTime != Long.MAX_VALUE) {
+ timeToNextEviction = minSleepPeriodInMs;
} else {
- final Instant now = Instant.now();
- timeToNextEviction = Duration.between(now, minNextEvictionTime);
+ final long now = System.currentTimeMillis();
+ timeToNextEviction = minNextEvictionTime - now;
}
return timeToNextEviction;
}
- private Instant evictStaleEntries() {
- Instant minNextEvictionTime = Instant.MAX;
+ private long evictStaleEntries() {
+ long minNextEvictionTime = Long.MAX_VALUE;
final Set> caches = new HashSet<>();
synchronized (lock) {
caches.addAll(weakCaches.keySet());
}
for (final HotEntryCache, ?> cache : caches) {
- final Instant nextEvictionTime = cache.evict();
- minNextEvictionTime = min(minNextEvictionTime, nextEvictionTime);
+ final long nextEvictionTime = cache.evict();
+ minNextEvictionTime = Math.min(minNextEvictionTime, nextEvictionTime);
}
return minNextEvictionTime;
}
- private void sleepToNextEviction(final Duration timeToNextEviction) {
+ private void sleepToNextEviction(final long timeToNextEviction) {
try {
- final Duration timeToSleep = minDuration(timeToNextEviction, maxSleepPeriod);
- final long timeToSleepMS = Math.max(timeToSleep.toMillis(), minSleepPeriod.toMillis());
+ final long timeToSleep = Math.min(timeToNextEviction, maxSleepPeriodInMs);
+ final long timeToSleepMS = Math.max(timeToSleep, minSleepPeriodInMs);
LOGGER.trace("sleeping {}ms", timeToSleepMS);
TimeUnit.MILLISECONDS.sleep(timeToSleepMS);
} catch (final InterruptedException e) {
@@ -247,12 +254,12 @@ public class HotEntryCache {
return result;
}
- public void setMinSleepPeriod(final Duration minSleepPeriod) {
- this.minSleepPeriod = minSleepPeriod;
+ public void setMinSleepPeriod(final long minSleepPeriodInMs) {
+ this.minSleepPeriodInMs = minSleepPeriodInMs;
}
- public void setMaxSleepPeriod(final Duration maxSleepPeriod) {
- this.maxSleepPeriod = maxSleepPeriod;
+ public void setMaxSleepPeriod(final long maxSleepPeriodInMs) {
+ this.maxSleepPeriodInMs = maxSleepPeriodInMs;
}
}
@@ -265,7 +272,7 @@ public class HotEntryCache {
TIME_UPDATER.start();
}
- private static Instant now;
+ private static long now;
/**
* Mapping of the key to the value.
@@ -286,7 +293,7 @@ public class HotEntryCache {
this.timeToLive = timeToLive;
this.clock = clock;
this.name = name;
- now = Instant.now(clock);
+ now = clock.millis();
EVICTER.addCache(this);
TIME_UPDATER.addCache(this);
@@ -316,20 +323,20 @@ public class HotEntryCache {
listeners.add(listener);
}
- static void setMinSleepPeriod(final Duration minSleepPeriod) {
- EVICTER.setMinSleepPeriod(minSleepPeriod);
- TIME_UPDATER.setUpdateInterval(minSleepPeriod.dividedBy(2));
+ static void setMinSleepPeriod(final long minSleepPeriodInMs) {
+ EVICTER.setMinSleepPeriod(minSleepPeriodInMs);
+ TIME_UPDATER.setUpdateInterval(minSleepPeriodInMs / 2);
}
- static void setMaxSleepPeriod(final Duration maxSleepPeriod) {
- EVICTER.setMaxSleepPeriod(maxSleepPeriod);
+ static void setMaxSleepPeriod(final long maxSleepPeriodInMs) {
+ EVICTER.setMaxSleepPeriod(maxSleepPeriodInMs);
}
- static Duration getMinSleepPeriod() {
+ static long getMinSleepPeriod() {
return EVICTER.getMinSleepPeriod();
}
- static Duration getMaxSleepPeriod() {
+ static long getMaxSleepPeriod() {
return EVICTER.getMaxSleepPeriod();
}
@@ -354,7 +361,7 @@ public class HotEntryCache {
oldEntry.setValue(value);
entry = oldEntry;
} else {
- final Instant creationTime = now();
+ final long creationTime = now();
entry = new Entry<>(value, creationTime);
}
touch(k, entry);
@@ -387,7 +394,7 @@ public class HotEntryCache {
final boolean wasEmptyBefore = cache.isEmpty();
final Entry entry = cache.computeIfAbsent(key, (k) -> {
final V value = mappingFunction.apply(k);
- final Instant creationTime = now();
+ final long creationTime = now();
final Entry e = new Entry<>(value, creationTime);
touch(key, e);
return e;
@@ -426,20 +433,18 @@ public class HotEntryCache {
});
}
- private Instant evict() {
- final Instant now = now();
- final Instant oldestValuesToKeep = now.minus(timeToLive);
- Instant lastAccessTime = Instant.MAX;
+ private long evict() {
+ final long now = now();
+ final long oldestValuesToKeep = now - timeToLive.toMillis();
+ long lastAccessTime = Long.MAX_VALUE;
LOGGER.trace("{}: cache size before eviction {}", name, cache.size());
- // for (final java.util.Map.Entry> mapEntry :
- // lastAccessMap.entrySet()) {
for (final java.util.Map.Entry> mapEntry : cache.entrySet()) {
final Entry entry = mapEntry.getValue();
- final Instant lastAccessed = entry.getLastAccessed();
- lastAccessTime = min(lastAccessTime, lastAccessed);
+ final long lastAccessed = entry.getLastAccessed();
+ lastAccessTime = Math.min(lastAccessTime, lastAccessed);
- if (lastAccessed.isAfter(oldestValuesToKeep)) {
+ if (lastAccessed > oldestValuesToKeep) {
continue;
}
@@ -456,38 +461,30 @@ public class HotEntryCache {
}
LOGGER.trace("{}: cache size after eviction {}", name, cache.size());
- final Instant nextEvictionTime = lastAccessTime.equals(Instant.MAX) ? Instant.MAX
- : lastAccessTime.plus(timeToLive);
+ final long nextEvictionTime = lastAccessTime == Long.MAX_VALUE ? Long.MAX_VALUE
+ : lastAccessTime + timeToLive.toMillis();
return nextEvictionTime;
}
- private static Instant min(final Instant a, final Instant b) {
- return a.isBefore(b) ? a : b;
- }
-
- private static Duration minDuration(final Duration a, final Duration b) {
- return a.compareTo(b) < 0 ? a : b;
- }
-
- private Instant now() {
+ private long now() {
return now;
}
// visible for test
void updateTime() {
- now = Instant.now(clock);
+ now = clock.millis();
}
private void touch(final K key, final Entry entry) {
if (entry != null) {
- final Instant now = now();
+ final long now = now();
entry.touch(now);
}
}
- private boolean isExpired(final Entry entry, final Instant now) {
- return entry.getLastAccessed().plus(timeToLive).isBefore(now);
+ private boolean isExpired(final Entry entry, final long now) {
+ return (entry.getLastAccessed() + timeToLive.toMillis()) < now;
}
private void handleEvent(final K key, final V value) {
diff --git a/pdb-utils/src/test/java/org/lucares/utils/cache/HotEntryCacheTest.java b/pdb-utils/src/test/java/org/lucares/utils/cache/HotEntryCacheTest.java
index 59173e5..5e5955e 100644
--- a/pdb-utils/src/test/java/org/lucares/utils/cache/HotEntryCacheTest.java
+++ b/pdb-utils/src/test/java/org/lucares/utils/cache/HotEntryCacheTest.java
@@ -30,16 +30,16 @@ public class HotEntryCacheTest {
@Test(invocationCount = 1)
public void testRemovalListenerCalledOnExpire() throws InterruptedException {
- final Duration originalMinSleepPeriod = HotEntryCache.getMinSleepPeriod();
- final Duration originalMaxSleepPeriod = HotEntryCache.getMaxSleepPeriod();
+ final long originalMinSleepPeriod = HotEntryCache.getMinSleepPeriod();
+ final long originalMaxSleepPeriod = HotEntryCache.getMaxSleepPeriod();
try {
final String key = "key";
final String value = "value";
final CountDownLatch latch = new CountDownLatch(1);
final HotEntryCache cache = new HotEntryCache<>(Duration.ofMillis(1), "cache-" + ++cacheId);
- HotEntryCache.setMinSleepPeriod(Duration.ofMillis(1));
- HotEntryCache.setMaxSleepPeriod(Duration.ofMillis(10));
+ HotEntryCache.setMinSleepPeriod(1);
+ HotEntryCache.setMaxSleepPeriod(10);
cache.addListener((k, v) -> {
Assert.assertEquals(k, key);
Assert.assertEquals(v, value);