add maxSize parameter to HotEntryCache
This commit is contained in:
@@ -14,12 +14,12 @@ import java.util.concurrent.ExecutionException;
|
||||
import java.util.concurrent.Future;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
import java.util.concurrent.TimeoutException;
|
||||
import java.util.concurrent.atomic.AtomicInteger;
|
||||
import java.util.concurrent.atomic.AtomicReference;
|
||||
import java.util.function.Consumer;
|
||||
import java.util.function.Function;
|
||||
|
||||
import javax.annotation.concurrent.GuardedBy;
|
||||
|
||||
import org.lucares.collections.LongList;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
@@ -74,7 +74,7 @@ public class HotEntryCache<K, V> {
|
||||
}
|
||||
|
||||
private final static class Entry<V> {
|
||||
private long lastAccessed;
|
||||
private volatile long lastAccessed;
|
||||
|
||||
private V value;
|
||||
|
||||
@@ -135,7 +135,7 @@ public class HotEntryCache<K, V> {
|
||||
synchronized (lock) {
|
||||
keySet.addAll(weakCaches.keySet());
|
||||
}
|
||||
LOGGER.trace("update time");
|
||||
// LOGGER.trace("update time");
|
||||
for (final HotEntryCache<?, ?> cache : keySet) {
|
||||
cache.updateTime();
|
||||
}
|
||||
@@ -149,7 +149,6 @@ public class HotEntryCache<K, V> {
|
||||
|
||||
private static final class EvictionThread extends Thread {
|
||||
|
||||
@GuardedBy("lock")
|
||||
private final WeakHashMap<HotEntryCache<?, ?>, Void> weakCaches = new WeakHashMap<>();
|
||||
private final AtomicReference<CompletableFuture<Void>> future = new AtomicReference<>(null);
|
||||
private final Object lock = new Object();
|
||||
@@ -219,6 +218,7 @@ public class HotEntryCache<K, V> {
|
||||
caches.addAll(weakCaches.keySet());
|
||||
}
|
||||
for (final HotEntryCache<?, ?> cache : caches) {
|
||||
// TODO remember nextEvictionTime on cache so that we can skip a cache
|
||||
final long nextEvictionTime = cache.evict();
|
||||
minNextEvictionTime = Math.min(minNextEvictionTime, nextEvictionTime);
|
||||
}
|
||||
@@ -272,8 +272,6 @@ public class HotEntryCache<K, V> {
|
||||
TIME_UPDATER.start();
|
||||
}
|
||||
|
||||
private static long now;
|
||||
|
||||
/**
|
||||
* Mapping of the key to the value.
|
||||
* <p>
|
||||
@@ -283,14 +281,19 @@ public class HotEntryCache<K, V> {
|
||||
|
||||
private final CopyOnWriteArrayList<EventListener<K, V>> listeners = new CopyOnWriteArrayList<>();
|
||||
|
||||
private final Duration timeToLive;
|
||||
private final long timeToLive;
|
||||
|
||||
private volatile long now = 0;
|
||||
|
||||
private Clock clock;
|
||||
|
||||
private final String name;
|
||||
|
||||
HotEntryCache(final Duration timeToLive, final Clock clock, final String name) {
|
||||
this.timeToLive = timeToLive;
|
||||
private int maxSize;
|
||||
|
||||
HotEntryCache(final Duration timeToLive, final int maxSize, final Clock clock, final String name) {
|
||||
this.timeToLive = timeToLive.toMillis();
|
||||
this.maxSize = maxSize;
|
||||
this.clock = clock;
|
||||
this.name = name;
|
||||
now = clock.millis();
|
||||
@@ -299,16 +302,16 @@ public class HotEntryCache<K, V> {
|
||||
TIME_UPDATER.addCache(this);
|
||||
}
|
||||
|
||||
HotEntryCache(final Duration timeToLive, final Clock clock) {
|
||||
this(timeToLive, clock, UUID.randomUUID().toString());
|
||||
HotEntryCache(final Duration timeToLive, final int maxSize, final Clock clock) {
|
||||
this(timeToLive, maxSize, clock, UUID.randomUUID().toString());
|
||||
}
|
||||
|
||||
public HotEntryCache(final Duration timeToLive, final String name) {
|
||||
this(timeToLive, Clock.systemDefaultZone(), name);
|
||||
public HotEntryCache(final Duration timeToLive, final int maxSize, final String name) {
|
||||
this(timeToLive, maxSize, Clock.systemDefaultZone(), name);
|
||||
}
|
||||
|
||||
public HotEntryCache(final Duration timeToLive) {
|
||||
this(timeToLive, Clock.systemDefaultZone(), UUID.randomUUID().toString());
|
||||
public HotEntryCache(final Duration timeToLive, final int maxSize) {
|
||||
this(timeToLive, maxSize, Clock.systemDefaultZone(), UUID.randomUUID().toString());
|
||||
}
|
||||
|
||||
public int size() {
|
||||
@@ -350,6 +353,8 @@ public class HotEntryCache<K, V> {
|
||||
|
||||
public V put(final K key, final V value) {
|
||||
|
||||
removeEldestCacheTooBig();
|
||||
|
||||
final boolean wasEmptyBefore = cache.isEmpty();
|
||||
final AtomicReference<V> oldValueAtomicReference = new AtomicReference<>();
|
||||
cache.compute(key, (k, oldEntry) -> {
|
||||
@@ -391,6 +396,8 @@ public class HotEntryCache<K, V> {
|
||||
*/
|
||||
public V putIfAbsent(final K key, final Function<K, V> mappingFunction) {
|
||||
|
||||
removeEldestCacheTooBig();
|
||||
|
||||
final boolean wasEmptyBefore = cache.isEmpty();
|
||||
final Entry<V> entry = cache.computeIfAbsent(key, (k) -> {
|
||||
final V value = mappingFunction.apply(k);
|
||||
@@ -433,27 +440,76 @@ public class HotEntryCache<K, V> {
|
||||
});
|
||||
}
|
||||
|
||||
private void removeEldestCacheTooBig() {
|
||||
if (cache.size() >= maxSize) {
|
||||
removeEldest();
|
||||
}
|
||||
}
|
||||
|
||||
private synchronized void removeEldest() {
|
||||
|
||||
if (cache.size() >= maxSize) {
|
||||
final LongList lastAccessTimes = new LongList(cache.size());
|
||||
for (final java.util.Map.Entry<K, Entry<V>> mapEntry : cache.entrySet()) {
|
||||
final Entry<V> entry = mapEntry.getValue();
|
||||
final long lastAccessed = entry.getLastAccessed();
|
||||
lastAccessTimes.add(lastAccessed);
|
||||
}
|
||||
|
||||
lastAccessTimes.sort();
|
||||
|
||||
final int numEntriesToRemove = Math.max((int) (maxSize * 0.2), 1);
|
||||
|
||||
final long oldestValuesToKeep = lastAccessTimes.get(numEntriesToRemove - 1) + 1;
|
||||
evictInternal(oldestValuesToKeep, numEntriesToRemove);
|
||||
}
|
||||
}
|
||||
|
||||
private long evict() {
|
||||
final long now = now();
|
||||
final long oldestValuesToKeep = now - timeToLive.toMillis();
|
||||
long lastAccessTime = Long.MAX_VALUE;
|
||||
final long oldestValuesToKeep = now - timeToLive;
|
||||
LOGGER.trace("{}: oldestValuesToKeep = {} = {} - {}", name, oldestValuesToKeep, now, timeToLive);
|
||||
return evictInternal(oldestValuesToKeep, Integer.MAX_VALUE);
|
||||
}
|
||||
|
||||
private long evictInternal(final long oldestValuesToKeep, final int maxEntriesToRemove) {
|
||||
long oldestAccessTime = Long.MAX_VALUE;
|
||||
LOGGER.trace("{}: cache size before eviction {}", name, cache.size());
|
||||
LOGGER.trace("{}: oldest value to keep {}", name, oldestValuesToKeep);
|
||||
|
||||
final AtomicInteger removedEntries = new AtomicInteger();
|
||||
|
||||
for (final java.util.Map.Entry<K, Entry<V>> mapEntry : cache.entrySet()) {
|
||||
final Entry<V> entry = mapEntry.getValue();
|
||||
final long lastAccessed = entry.getLastAccessed();
|
||||
lastAccessTime = Math.min(lastAccessTime, lastAccessed);
|
||||
oldestAccessTime = Math.min(oldestAccessTime, lastAccessed);
|
||||
|
||||
if (lastAccessed > oldestValuesToKeep) {
|
||||
if (removedEntries.get() >= maxEntriesToRemove) {
|
||||
// finish iterating over all entries so that this method return the correct
|
||||
// nextEvictionTime
|
||||
LOGGER.trace("{}: removedEntries >= maxEntriesToRemove = {} >= {}", name, removedEntries.get(),
|
||||
maxEntriesToRemove);
|
||||
continue;
|
||||
}
|
||||
|
||||
if (lastAccessed >= oldestValuesToKeep) {
|
||||
LOGGER.trace("{}: lastAccessed >= oldestValuesToKeep = {} >= {}", name, lastAccessed,
|
||||
oldestValuesToKeep);
|
||||
continue;
|
||||
}
|
||||
|
||||
final K keyToBeRemoved = mapEntry.getKey();
|
||||
LOGGER.trace("{}: atomically removing key {}", name, keyToBeRemoved);
|
||||
|
||||
cache.computeIfPresent(keyToBeRemoved, (k, e) -> {
|
||||
if (isExpired(e, now)) {
|
||||
|
||||
if (entry.getLastAccessed() < oldestValuesToKeep) {
|
||||
LOGGER.trace("{}: removing value from {}", name, entry.getLastAccessed());
|
||||
removedEntries.incrementAndGet();
|
||||
handleEvent(k, e.getValue());
|
||||
return null;
|
||||
} else {
|
||||
LOGGER.trace("{}: keeping value from {}", name, entry.getLastAccessed());
|
||||
}
|
||||
return e;
|
||||
});
|
||||
@@ -461,8 +517,8 @@ public class HotEntryCache<K, V> {
|
||||
}
|
||||
LOGGER.trace("{}: cache size after eviction {}", name, cache.size());
|
||||
|
||||
final long nextEvictionTime = lastAccessTime == Long.MAX_VALUE ? Long.MAX_VALUE
|
||||
: lastAccessTime + timeToLive.toMillis();
|
||||
final long nextEvictionTime = oldestAccessTime == Long.MAX_VALUE ? Long.MAX_VALUE
|
||||
: oldestAccessTime + timeToLive;
|
||||
return nextEvictionTime;
|
||||
}
|
||||
|
||||
@@ -473,6 +529,7 @@ public class HotEntryCache<K, V> {
|
||||
// visible for test
|
||||
void updateTime() {
|
||||
now = clock.millis();
|
||||
LOGGER.trace("{}: update time to {}", name, now);
|
||||
}
|
||||
|
||||
private void touch(final K key, final Entry<V> entry) {
|
||||
@@ -483,12 +540,9 @@ public class HotEntryCache<K, V> {
|
||||
}
|
||||
}
|
||||
|
||||
private boolean isExpired(final Entry<V> entry, final long now) {
|
||||
return (entry.getLastAccessed() + timeToLive.toMillis()) < now;
|
||||
}
|
||||
|
||||
private void handleEvent(final K key, final V value) {
|
||||
|
||||
LOGGER.trace("{}: calling {} listeners for {} -> {}", name, listeners.size(), key, value);
|
||||
for (final EventListener<K, V> eventSubscribers : listeners) {
|
||||
|
||||
eventSubscribers.onRemove(key, value);
|
||||
|
||||
Reference in New Issue
Block a user