diff --git a/core/src/main/java/org/elasticsearch/common/cache/Cache.java b/core/src/main/java/org/elasticsearch/common/cache/Cache.java index 2297df67655d9..df30123c35b42 100644 --- a/core/src/main/java/org/elasticsearch/common/cache/Cache.java +++ b/core/src/main/java/org/elasticsearch/common/cache/Cache.java @@ -34,6 +34,7 @@ import java.util.concurrent.locks.ReentrantLock; import java.util.concurrent.locks.ReentrantReadWriteLock; import java.util.function.BiFunction; +import java.util.function.Predicate; import java.util.function.ToLongBiFunction; /** @@ -193,33 +194,35 @@ private static class CacheSegment { SegmentStats segmentStats = new SegmentStats(); /** - * get an entry from the segment + * get an entry from the segment; expired entries will be returned as null but not removed from the cache until the LRU list is + * pruned or a manual {@link Cache#refresh()} is performed * - * @param key the key of the entry to get from the cache - * @param now the access time of this entry + * @param key the key of the entry to get from the cache + * @param now the access time of this entry + * @param isExpired test if the entry is expired * @return the entry if there was one, otherwise null */ - Entry get(K key, long now) { + Entry get(K key, long now, Predicate> isExpired) { CompletableFuture> future; Entry entry = null; try (ReleasableLock ignored = readLock.acquire()) { future = map.get(key); } if (future != null) { - try { - entry = future.handle((ok, ex) -> { - if (ok != null) { - segmentStats.hit(); - ok.accessTime = now; - return ok; - } else { - segmentStats.miss(); - return null; - } - }).get(); - } catch (ExecutionException | InterruptedException e) { - throw new IllegalStateException(e); - } + try { + entry = future.handle((ok, ex) -> { + if (ok != null && !isExpired.test(ok)) { + segmentStats.hit(); + ok.accessTime = now; + return ok; + } else { + segmentStats.miss(); + return null; + } + }).get(); + } catch (ExecutionException | InterruptedException e) { + throw new IllegalStateException(e); + } } else { segmentStats.miss(); @@ -332,8 +335,8 @@ public V get(K key) { private V get(K key, long now) { CacheSegment segment = getCacheSegment(key); - Entry entry = segment.get(key, now); - if (entry == null || isExpired(entry, now)) { + Entry entry = segment.get(key, now, e -> isExpired(e, now)); + if (entry == null) { return null; } else { promote(entry, now); diff --git a/core/src/test/java/org/elasticsearch/common/cache/CacheTests.java b/core/src/test/java/org/elasticsearch/common/cache/CacheTests.java index 71fe7b262a699..7dbaba02897c6 100644 --- a/core/src/test/java/org/elasticsearch/common/cache/CacheTests.java +++ b/core/src/test/java/org/elasticsearch/common/cache/CacheTests.java @@ -257,6 +257,28 @@ protected long now() { } } + public void testSimpleExpireAfterAccess() { + AtomicLong now = new AtomicLong(); + Cache cache = new Cache() { + @Override + protected long now() { + return now.get(); + } + }; + cache.setExpireAfterAccessNanos(1); + now.set(0); + for (int i = 0; i < numberOfEntries; i++) { + cache.put(i, Integer.toString(i)); + } + for (int i = 0; i < numberOfEntries; i++) { + assertEquals(cache.get(i), Integer.toString(i)); + } + now.set(2); + for(int i = 0; i < numberOfEntries; i++) { + assertNull(cache.get(i)); + } + } + public void testExpirationAfterWrite() { AtomicLong now = new AtomicLong(); Cache cache = new Cache() {