diff --git a/caffeine/src/main/java/com/github/benmanes/caffeine/cache/BoundedLocalCache.java b/caffeine/src/main/java/com/github/benmanes/caffeine/cache/BoundedLocalCache.java index 84ed3afd8c..aaa8af7d35 100644 --- a/caffeine/src/main/java/com/github/benmanes/caffeine/cache/BoundedLocalCache.java +++ b/caffeine/src/main/java/com/github/benmanes/caffeine/cache/BoundedLocalCache.java @@ -678,21 +678,20 @@ void expireAfterAccessEntries(long now) { return; } - long expirationTime = (now - expiresAfterAccessNanos()); - expireAfterAccessEntries(accessOrderEdenDeque(), expirationTime, now); + expireAfterAccessEntries(accessOrderEdenDeque(), now); if (evicts()) { - expireAfterAccessEntries(accessOrderProbationDeque(), expirationTime, now); - expireAfterAccessEntries(accessOrderProtectedDeque(), expirationTime, now); + expireAfterAccessEntries(accessOrderProbationDeque(), now); + expireAfterAccessEntries(accessOrderProtectedDeque(), now); } } /** Expires entries in an access-order queue. */ @GuardedBy("evictionLock") - void expireAfterAccessEntries(AccessOrderDeque> accessOrderDeque, - long expirationTime, long now) { + void expireAfterAccessEntries(AccessOrderDeque> accessOrderDeque, long now) { + long duration = expiresAfterAccessNanos(); for (;;) { Node node = accessOrderDeque.peekFirst(); - if ((node == null) || (node.getAccessTime() > expirationTime)) { + if ((node == null) || ((now - node.getAccessTime()) < duration)) { return; } evictEntry(node, RemovalCause.EXPIRED, now); @@ -705,10 +704,10 @@ void expireAfterWriteEntries(long now) { if (!expiresAfterWrite()) { return; } - long expirationTime = now - expiresAfterWriteNanos(); + long duration = expiresAfterWriteNanos(); for (;;) { final Node node = writeOrderDeque().peekFirst(); - if ((node == null) || (node.getWriteTime() > expirationTime)) { + if ((node == null) || ((now - node.getWriteTime()) < duration)) { break; } evictEntry(node, RemovalCause.EXPIRED, now); @@ -762,12 +761,10 @@ boolean evictEntry(Node node, RemovalCause cause, long now) { if (actualCause[0] == RemovalCause.EXPIRED) { boolean expired = false; if (expiresAfterAccess()) { - long expirationTime = now - expiresAfterAccessNanos(); - expired |= (n.getAccessTime() <= expirationTime); + expired |= ((now - n.getAccessTime()) >= expiresAfterAccessNanos()); } if (expiresAfterWrite()) { - long expirationTime = now - expiresAfterWriteNanos(); - expired |= (n.getWriteTime() <= expirationTime); + expired |= ((now - n.getWriteTime()) >= expiresAfterWriteNanos()); } if (expiresVariable()) { expired |= (n.getVariableTime() <= now); @@ -1333,10 +1330,10 @@ public void run() { if (isComputingAsync(node)) { synchronized (node) { if (!Async.isReady((CompletableFuture) node.getValue())) { - long expirationTime = expirationTicker().read() + Async.MAXIMUM_EXPIRY; - setWriteTime(node, expirationTime); - setAccessTime(node, expirationTime); + long expirationTime = expirationTicker().read() + Long.MAX_VALUE; setVariableTime(node, expirationTime); + setAccessTime(node, expirationTime); + setWriteTime(node, expirationTime); } } } @@ -1745,9 +1742,8 @@ public V remove(Object key) { * @return the removed value or null if no mapping was found */ V removeNoWriter(Object key) { - Node node; - Object lookupKey = nodeFactory.newLookupKey(key); - if (!data.containsKey(lookupKey) || ((node = data.remove(lookupKey)) == null)) { + Node node = data.remove(nodeFactory.newLookupKey(key)); + if (node == null) { return null; } @@ -1822,9 +1818,7 @@ V removeWithWriter(Object key) { @Override public boolean remove(Object key, Object value) { requireNonNull(key); - - Object lookupKey = nodeFactory.newLookupKey(key); - if ((value == null) || !data.containsKey(lookupKey)) { + if (value == null) { return false; } @@ -1837,7 +1831,7 @@ public boolean remove(Object key, Object value) { RemovalCause[] cause = new RemovalCause[1]; long now = expirationTicker().read(); - data.computeIfPresent(lookupKey, (kR, node) -> { + data.computeIfPresent(nodeFactory.newLookupKey(key), (kR, node) -> { synchronized (node) { oldKey[0] = node.getKey(); oldValue[0] = node.getValue(); diff --git a/caffeine/src/main/java/com/github/benmanes/caffeine/cache/LocalAsyncLoadingCache.java b/caffeine/src/main/java/com/github/benmanes/caffeine/cache/LocalAsyncLoadingCache.java index e8d57947bb..7b90743285 100644 --- a/caffeine/src/main/java/com/github/benmanes/caffeine/cache/LocalAsyncLoadingCache.java +++ b/caffeine/src/main/java/com/github/benmanes/caffeine/cache/LocalAsyncLoadingCache.java @@ -559,6 +559,11 @@ public int size() { return delegate.size(); } + @Override + public void clear() { + delegate.clear(); + } + @Override public boolean containsKey(Object key) { return delegate.containsKey(key); @@ -603,6 +608,28 @@ public V remove(Object key) { return Async.getWhenSuccessful(oldValueFuture); } + @Override + public boolean remove(Object key, Object value) { + requireNonNull(key); + if (value == null) { + return false; + } + CompletableFuture oldValueFuture = delegate.get(key); + if ((oldValueFuture != null) && !value.equals(Async.getWhenSuccessful(oldValueFuture))) { + // Optimistically check if the current value is equal, but don't skip if it may be loading + return false; + } + + @SuppressWarnings("unchecked") + K castedKey = (K) key; + boolean[] removed = { false }; + delegate.compute(castedKey, (k, oldValue) -> { + removed[0] = value.equals(Async.getWhenSuccessful(oldValue)); + return removed[0] ? null : oldValue; + }, /* recordStats */ false, /* recordLoad */ false); + return removed[0]; + } + @Override public V replace(K key, V value) { requireNonNull(value); @@ -616,24 +643,19 @@ public boolean replace(K key, V oldValue, V newValue) { requireNonNull(oldValue); requireNonNull(newValue); CompletableFuture oldValueFuture = delegate.get(key); - return oldValue.equals(Async.getIfReady(oldValueFuture)) - && delegate.replace(key, oldValueFuture, CompletableFuture.completedFuture(newValue)); - } - - @Override - public boolean remove(Object key, Object value) { - requireNonNull(key); - if (value == null) { + if ((oldValueFuture != null) && !oldValue.equals(Async.getWhenSuccessful(oldValueFuture))) { + // Optimistically check if the current value is equal, but don't skip if it may be loading return false; } - CompletableFuture oldValueFuture = delegate.get(key); - return value.equals(Async.getIfReady(oldValueFuture)) - && delegate.remove(key, oldValueFuture); - } - @Override - public void clear() { - delegate.clear(); + @SuppressWarnings("unchecked") + K castedKey = key; + boolean[] replaced = { false }; + delegate.compute(castedKey, (k, value) -> { + replaced[0] = oldValue.equals(Async.getWhenSuccessful(value)); + return replaced[0] ? CompletableFuture.completedFuture(newValue) : value; + }, /* recordStats */ false, /* recordLoad */ false); + return replaced[0]; } @Override diff --git a/caffeine/src/main/java/com/github/benmanes/caffeine/cache/UnboundedLocalCache.java b/caffeine/src/main/java/com/github/benmanes/caffeine/cache/UnboundedLocalCache.java index b08975518b..d45e4e5a09 100644 --- a/caffeine/src/main/java/com/github/benmanes/caffeine/cache/UnboundedLocalCache.java +++ b/caffeine/src/main/java/com/github/benmanes/caffeine/cache/UnboundedLocalCache.java @@ -327,6 +327,11 @@ public boolean isEmpty() { return data.isEmpty(); } + @Override + public int size() { + return data.size(); + } + @Override public void clear() { if (!hasRemovalListener() && (writer == CacheWriter.disabledWriter())) { @@ -338,11 +343,6 @@ public void clear() { } } - @Override - public int size() { - return data.size(); - } - @Override public boolean containsKey(Object key) { return data.containsKey(key); diff --git a/caffeine/src/test/java/com/github/benmanes/caffeine/cache/ExpirationTest.java b/caffeine/src/test/java/com/github/benmanes/caffeine/cache/ExpirationTest.java index b0efae5ad8..57b68ed4e7 100644 --- a/caffeine/src/test/java/com/github/benmanes/caffeine/cache/ExpirationTest.java +++ b/caffeine/src/test/java/com/github/benmanes/caffeine/cache/ExpirationTest.java @@ -71,7 +71,7 @@ public final class ExpirationTest { @Test(dataProvider = "caches") @CacheSpec(mustExpiresWithAnyOf = { AFTER_ACCESS, AFTER_WRITE, VARIABLE }, - expiry = { CacheExpiry.DISABLED, CacheExpiry.WRITE, CacheExpiry.ACCESS }, + expiry = { CacheExpiry.DISABLED, CacheExpiry.CREATE, CacheExpiry.WRITE, CacheExpiry.ACCESS }, expireAfterAccess = {Expire.DISABLED, Expire.IMMEDIATELY}, expireAfterWrite = {Expire.DISABLED, Expire.IMMEDIATELY}, expiryTime = Expire.IMMEDIATELY, population = Population.EMPTY) @@ -97,7 +97,7 @@ public void expire_zero(Cache cache, CacheContext context) { @CacheSpec(implementation = Implementation.Caffeine, keys = ReferenceType.STRONG, population = Population.FULL, writer = Writer.EXCEPTIONAL, mustExpiresWithAnyOf = { AFTER_ACCESS, AFTER_WRITE, VARIABLE }, - expiry = { CacheExpiry.DISABLED, CacheExpiry.WRITE, CacheExpiry.ACCESS }, + expiry = { CacheExpiry.DISABLED, CacheExpiry.CREATE, CacheExpiry.WRITE, CacheExpiry.ACCESS }, expireAfterAccess = {Expire.DISABLED, Expire.ONE_MINUTE}, expiryTime = Expire.ONE_MINUTE, expireAfterWrite = {Expire.DISABLED, Expire.ONE_MINUTE}, compute = Compute.SYNC, executorFailure = ExecutorFailure.EXPECTED, removalListener = Listener.REJECTING) @@ -116,7 +116,7 @@ public void getIfPresent_writerFails(Cache cache, CacheContext @CacheSpec(implementation = Implementation.Caffeine, keys = ReferenceType.STRONG, population = Population.FULL, expiryTime = Expire.ONE_MINUTE, mustExpiresWithAnyOf = { AFTER_ACCESS, AFTER_WRITE, VARIABLE }, - expiry = { CacheExpiry.DISABLED, CacheExpiry.WRITE, CacheExpiry.ACCESS }, + expiry = { CacheExpiry.DISABLED, CacheExpiry.CREATE, CacheExpiry.WRITE, CacheExpiry.ACCESS }, expireAfterAccess = {Expire.DISABLED, Expire.ONE_MINUTE}, expireAfterWrite = {Expire.DISABLED, Expire.ONE_MINUTE}, compute = Compute.SYNC, writer = Writer.EXCEPTIONAL, removalListener = Listener.REJECTING) @@ -134,7 +134,7 @@ public void get_writerFails(Cache cache, CacheContext context) @Test(dataProvider = "caches") @CacheSpec(population = Population.FULL, expiryTime = Expire.ONE_MINUTE, mustExpiresWithAnyOf = { AFTER_ACCESS, AFTER_WRITE, VARIABLE }, - expiry = { CacheExpiry.DISABLED, CacheExpiry.WRITE, CacheExpiry.ACCESS }, + expiry = { CacheExpiry.DISABLED, CacheExpiry.CREATE, CacheExpiry.WRITE, CacheExpiry.ACCESS }, expireAfterAccess = {Expire.DISABLED, Expire.ONE_MINUTE}, expireAfterWrite = {Expire.DISABLED, Expire.ONE_MINUTE}) public void put_insert(Cache cache, CacheContext context) { @@ -180,7 +180,7 @@ public void put_replace(Cache cache, CacheContext context) { @CacheSpec(implementation = Implementation.Caffeine, keys = ReferenceType.STRONG, population = Population.FULL, expiryTime = Expire.ONE_MINUTE, mustExpiresWithAnyOf = { AFTER_ACCESS, AFTER_WRITE, VARIABLE }, - expiry = { CacheExpiry.DISABLED, CacheExpiry.WRITE, CacheExpiry.ACCESS }, + expiry = { CacheExpiry.DISABLED, CacheExpiry.CREATE, CacheExpiry.WRITE, CacheExpiry.ACCESS }, expireAfterAccess = {Expire.DISABLED, Expire.ONE_MINUTE}, expireAfterWrite = {Expire.DISABLED, Expire.ONE_MINUTE}, compute = Compute.SYNC, writer = Writer.EXCEPTIONAL, removalListener = Listener.REJECTING) @@ -198,7 +198,7 @@ public void put_writerFails(Cache cache, CacheContext context) @Test(dataProvider = "caches") @CacheSpec(population = Population.FULL, expiryTime = Expire.ONE_MINUTE, mustExpiresWithAnyOf = { AFTER_ACCESS, AFTER_WRITE, VARIABLE }, - expiry = { CacheExpiry.DISABLED, CacheExpiry.WRITE, CacheExpiry.ACCESS }, + expiry = { CacheExpiry.DISABLED, CacheExpiry.CREATE, CacheExpiry.WRITE, CacheExpiry.ACCESS }, expireAfterAccess = {Expire.DISABLED, Expire.ONE_MINUTE}, expireAfterWrite = {Expire.DISABLED, Expire.ONE_MINUTE}) public void putAll_insert(Cache cache, CacheContext context) { @@ -245,7 +245,7 @@ public void putAll_replace(Cache cache, CacheContext context) @CacheSpec(implementation = Implementation.Caffeine, keys = ReferenceType.STRONG, population = Population.FULL, expiryTime = Expire.ONE_MINUTE, mustExpiresWithAnyOf = { AFTER_ACCESS, AFTER_WRITE, VARIABLE }, - expiry = { CacheExpiry.DISABLED, CacheExpiry.WRITE, CacheExpiry.ACCESS }, + expiry = { CacheExpiry.DISABLED, CacheExpiry.CREATE, CacheExpiry.WRITE, CacheExpiry.ACCESS }, expireAfterAccess = {Expire.DISABLED, Expire.ONE_MINUTE}, expireAfterWrite = {Expire.DISABLED, Expire.ONE_MINUTE}, compute = Compute.SYNC, writer = Writer.EXCEPTIONAL, removalListener = Listener.REJECTING) @@ -263,7 +263,7 @@ public void putAll_writerFails(Cache cache, CacheContext conte @Test(dataProvider = "caches") @CacheSpec(population = Population.FULL, expiryTime = Expire.ONE_MINUTE, mustExpiresWithAnyOf = { AFTER_ACCESS, AFTER_WRITE, VARIABLE }, - expiry = { CacheExpiry.DISABLED, CacheExpiry.WRITE, CacheExpiry.ACCESS }, + expiry = { CacheExpiry.DISABLED, CacheExpiry.CREATE, CacheExpiry.WRITE, CacheExpiry.ACCESS }, expireAfterAccess = {Expire.DISABLED, Expire.ONE_MINUTE}, expireAfterWrite = {Expire.DISABLED, Expire.ONE_MINUTE}) public void invalidate(Cache cache, CacheContext context) { @@ -279,7 +279,7 @@ public void invalidate(Cache cache, CacheContext context) { @CacheSpec(implementation = Implementation.Caffeine, keys = ReferenceType.STRONG, population = Population.FULL, expiryTime = Expire.ONE_MINUTE, mustExpiresWithAnyOf = { AFTER_ACCESS, AFTER_WRITE, VARIABLE }, - expiry = { CacheExpiry.DISABLED, CacheExpiry.WRITE, CacheExpiry.ACCESS }, + expiry = { CacheExpiry.DISABLED, CacheExpiry.CREATE, CacheExpiry.WRITE, CacheExpiry.ACCESS }, expireAfterAccess = {Expire.DISABLED, Expire.ONE_MINUTE}, expireAfterWrite = {Expire.DISABLED, Expire.ONE_MINUTE}, compute = Compute.SYNC, writer = Writer.EXCEPTIONAL, removalListener = Listener.REJECTING) @@ -297,7 +297,7 @@ public void invalidate_writerFails(Cache cache, CacheContext c @Test(dataProvider = "caches") @CacheSpec(population = Population.FULL, expiryTime = Expire.ONE_MINUTE, mustExpiresWithAnyOf = { AFTER_ACCESS, AFTER_WRITE, VARIABLE }, - expiry = { CacheExpiry.DISABLED, CacheExpiry.WRITE, CacheExpiry.ACCESS }, + expiry = { CacheExpiry.DISABLED, CacheExpiry.CREATE, CacheExpiry.WRITE, CacheExpiry.ACCESS }, expireAfterAccess = {Expire.DISABLED, Expire.ONE_MINUTE}, expireAfterWrite = {Expire.DISABLED, Expire.ONE_MINUTE}) public void invalidateAll(Cache cache, CacheContext context) { @@ -313,7 +313,7 @@ public void invalidateAll(Cache cache, CacheContext context) { @CacheSpec(implementation = Implementation.Caffeine, keys = ReferenceType.STRONG, population = Population.FULL, expiryTime = Expire.ONE_MINUTE, mustExpiresWithAnyOf = { AFTER_ACCESS, AFTER_WRITE, VARIABLE }, - expiry = { CacheExpiry.DISABLED, CacheExpiry.WRITE, CacheExpiry.ACCESS }, + expiry = { CacheExpiry.DISABLED, CacheExpiry.CREATE, CacheExpiry.WRITE, CacheExpiry.ACCESS }, expireAfterAccess = {Expire.DISABLED, Expire.ONE_MINUTE}, expireAfterWrite = {Expire.DISABLED, Expire.ONE_MINUTE}, compute = Compute.SYNC, writer = Writer.EXCEPTIONAL, removalListener = Listener.REJECTING) @@ -331,7 +331,7 @@ public void invalidateAll_writerFails(Cache cache, CacheContex @Test(dataProvider = "caches") @CacheSpec(population = Population.FULL, expiryTime = Expire.ONE_MINUTE, mustExpiresWithAnyOf = { AFTER_ACCESS, AFTER_WRITE, VARIABLE }, - expiry = { CacheExpiry.DISABLED, CacheExpiry.WRITE, CacheExpiry.ACCESS }, + expiry = { CacheExpiry.DISABLED, CacheExpiry.CREATE, CacheExpiry.WRITE, CacheExpiry.ACCESS }, expireAfterAccess = {Expire.DISABLED, Expire.ONE_MINUTE}, expireAfterWrite = {Expire.DISABLED, Expire.ONE_MINUTE}) public void invalidateAll_full(Cache cache, CacheContext context) { @@ -347,7 +347,7 @@ public void invalidateAll_full(Cache cache, CacheContext conte @CacheSpec(implementation = Implementation.Caffeine, keys = ReferenceType.STRONG, population = Population.FULL, expiryTime = Expire.ONE_MINUTE, mustExpiresWithAnyOf = { AFTER_ACCESS, AFTER_WRITE, VARIABLE }, - expiry = { CacheExpiry.DISABLED, CacheExpiry.WRITE, CacheExpiry.ACCESS }, + expiry = { CacheExpiry.DISABLED, CacheExpiry.CREATE, CacheExpiry.WRITE, CacheExpiry.ACCESS }, expireAfterAccess = {Expire.DISABLED, Expire.ONE_MINUTE}, expireAfterWrite = {Expire.DISABLED, Expire.ONE_MINUTE}, compute = Compute.SYNC, writer = Writer.EXCEPTIONAL, removalListener = Listener.REJECTING) @@ -365,7 +365,7 @@ public void invalidateAll_full_writerFails(Cache cache, CacheC @Test(dataProvider = "caches") @CacheSpec(population = { Population.SINGLETON, Population.PARTIAL, Population.FULL }, mustExpiresWithAnyOf = { AFTER_ACCESS, AFTER_WRITE, VARIABLE }, - expiry = { CacheExpiry.DISABLED, CacheExpiry.WRITE, CacheExpiry.ACCESS }, + expiry = { CacheExpiry.DISABLED, CacheExpiry.CREATE, CacheExpiry.WRITE, CacheExpiry.ACCESS }, expireAfterAccess = {Expire.DISABLED, Expire.ONE_MINUTE}, expireAfterWrite = {Expire.DISABLED, Expire.ONE_MINUTE}, expiryTime = Expire.ONE_MINUTE) @@ -377,7 +377,7 @@ public void estimatedSize(Cache cache, CacheContext context) { @Test(dataProvider = "caches") @CacheSpec(population = { Population.SINGLETON, Population.PARTIAL, Population.FULL }, mustExpiresWithAnyOf = { AFTER_ACCESS, AFTER_WRITE, VARIABLE }, - expiry = { CacheExpiry.DISABLED, CacheExpiry.WRITE, CacheExpiry.ACCESS }, + expiry = { CacheExpiry.DISABLED, CacheExpiry.CREATE, CacheExpiry.WRITE, CacheExpiry.ACCESS }, expireAfterAccess = {Expire.DISABLED, Expire.ONE_MINUTE}, expireAfterWrite = {Expire.DISABLED, Expire.ONE_MINUTE}, expiryTime = Expire.ONE_MINUTE) @@ -395,7 +395,7 @@ public void cleanUp(Cache cache, CacheContext context) { @CacheSpec(implementation = Implementation.Caffeine, keys = ReferenceType.STRONG, population = Population.FULL, expiryTime = Expire.ONE_MINUTE, mustExpiresWithAnyOf = { AFTER_ACCESS, AFTER_WRITE, VARIABLE }, - expiry = { CacheExpiry.DISABLED, CacheExpiry.WRITE, CacheExpiry.ACCESS }, + expiry = { CacheExpiry.DISABLED, CacheExpiry.CREATE, CacheExpiry.WRITE, CacheExpiry.ACCESS }, expireAfterAccess = {Expire.DISABLED, Expire.ONE_MINUTE}, expireAfterWrite = {Expire.DISABLED, Expire.ONE_MINUTE}, compute = Compute.SYNC, writer = Writer.EXCEPTIONAL, removalListener = Listener.REJECTING) @@ -413,7 +413,7 @@ public void cleanUp_writerFails(Cache cache, CacheContext cont @CacheSpec(implementation = Implementation.Caffeine, keys = ReferenceType.STRONG, population = Population.FULL, expiryTime = Expire.ONE_MINUTE, mustExpiresWithAnyOf = { AFTER_ACCESS, AFTER_WRITE, VARIABLE }, - expiry = { CacheExpiry.DISABLED, CacheExpiry.WRITE, CacheExpiry.ACCESS }, + expiry = { CacheExpiry.DISABLED, CacheExpiry.CREATE, CacheExpiry.WRITE, CacheExpiry.ACCESS }, expireAfterAccess = {Expire.DISABLED, Expire.ONE_MINUTE}, expireAfterWrite = {Expire.DISABLED, Expire.ONE_MINUTE}, compute = Compute.SYNC, writer = Writer.EXCEPTIONAL, removalListener = Listener.REJECTING) @@ -432,7 +432,7 @@ public void get_writerFails(LoadingCache cache, CacheContext c @CacheSpec(implementation = Implementation.Caffeine, keys = ReferenceType.STRONG, population = Population.FULL, expiryTime = Expire.ONE_MINUTE, mustExpiresWithAnyOf = { AFTER_ACCESS, AFTER_WRITE, VARIABLE }, - expiry = { CacheExpiry.DISABLED, CacheExpiry.WRITE, CacheExpiry.ACCESS }, + expiry = { CacheExpiry.DISABLED, CacheExpiry.CREATE, CacheExpiry.WRITE, CacheExpiry.ACCESS }, expireAfterAccess = {Expire.DISABLED, Expire.ONE_MINUTE}, expireAfterWrite = {Expire.DISABLED, Expire.ONE_MINUTE}, compute = Compute.SYNC, writer = Writer.EXCEPTIONAL, removalListener = Listener.REJECTING) @@ -450,7 +450,7 @@ public void getAll_writerFails(LoadingCache cache, CacheContex @Test(dataProvider = "caches") @CacheSpec(population = Population.FULL, loader = Loader.IDENTITY, expiryTime = Expire.ONE_MINUTE, mustExpiresWithAnyOf = { AFTER_ACCESS, AFTER_WRITE, VARIABLE }, - expiry = { CacheExpiry.DISABLED, CacheExpiry.WRITE, CacheExpiry.ACCESS }, + expiry = { CacheExpiry.DISABLED, CacheExpiry.CREATE, CacheExpiry.WRITE, CacheExpiry.ACCESS }, expireAfterAccess = {Expire.DISABLED, Expire.ONE_MINUTE}, expireAfterWrite = {Expire.DISABLED, Expire.ONE_MINUTE}) public void refresh(LoadingCache cache, CacheContext context) { @@ -469,7 +469,7 @@ public void refresh(LoadingCache cache, CacheContext context) @CacheSpec(implementation = Implementation.Caffeine, keys = ReferenceType.STRONG, population = Population.FULL, expiryTime = Expire.ONE_MINUTE, mustExpiresWithAnyOf = { AFTER_ACCESS, AFTER_WRITE, VARIABLE }, - expiry = { CacheExpiry.DISABLED, CacheExpiry.WRITE, CacheExpiry.ACCESS }, + expiry = { CacheExpiry.DISABLED, CacheExpiry.CREATE, CacheExpiry.WRITE, CacheExpiry.ACCESS }, expireAfterAccess = {Expire.DISABLED, Expire.ONE_MINUTE}, expireAfterWrite = {Expire.DISABLED, Expire.ONE_MINUTE}, compute = Compute.SYNC, writer = Writer.EXCEPTIONAL, removalListener = Listener.REJECTING) @@ -487,7 +487,7 @@ public void refresh_writerFails(LoadingCache cache, CacheConte @CacheSpec(population = Population.FULL, loader = Loader.IDENTITY, removalListener = Listener.CONSUMING, expiryTime = Expire.ONE_MINUTE, mustExpiresWithAnyOf = { AFTER_ACCESS, AFTER_WRITE, VARIABLE }, - expiry = { CacheExpiry.DISABLED, CacheExpiry.WRITE, CacheExpiry.ACCESS }, + expiry = { CacheExpiry.DISABLED, CacheExpiry.CREATE, CacheExpiry.WRITE, CacheExpiry.ACCESS }, expireAfterAccess = {Expire.DISABLED, Expire.ONE_MINUTE}, expireAfterWrite = {Expire.DISABLED, Expire.ONE_MINUTE}) @SuppressWarnings("FutureReturnValueIgnored") @@ -506,7 +506,7 @@ public void get(AsyncLoadingCache cache, CacheContext context) @Test(dataProvider = "caches") @CacheSpec(population = Population.EMPTY, removalListener = Listener.CONSUMING, mustExpiresWithAnyOf = { AFTER_ACCESS, AFTER_WRITE, VARIABLE }, - expiry = { CacheExpiry.DISABLED, CacheExpiry.WRITE, CacheExpiry.ACCESS }, + expiry = { CacheExpiry.DISABLED, CacheExpiry.CREATE, CacheExpiry.WRITE, CacheExpiry.ACCESS }, expireAfterAccess = {Expire.DISABLED, Expire.ONE_MINUTE}, expiryTime = Expire.ONE_MINUTE, expireAfterWrite = {Expire.DISABLED, Expire.ONE_MINUTE}) @SuppressWarnings("FutureReturnValueIgnored") @@ -530,12 +530,12 @@ public void get_async(AsyncLoadingCache cache, CacheContext co } @Test(dataProvider = "caches") - @CacheSpec(population = Population.FULL, removalListener = Listener.CONSUMING, + @CacheSpec(population = Population.SINGLETON, removalListener = Listener.CONSUMING, mustExpiresWithAnyOf = { AFTER_ACCESS, AFTER_WRITE, VARIABLE }, - expiry = { CacheExpiry.DISABLED, CacheExpiry.WRITE, CacheExpiry.ACCESS }, + expiry = { CacheExpiry.DISABLED, CacheExpiry.CREATE, CacheExpiry.WRITE, CacheExpiry.ACCESS }, expireAfterAccess = {Expire.DISABLED, Expire.ONE_MINUTE}, expireAfterWrite = {Expire.DISABLED, Expire.ONE_MINUTE}, - expiryTime = Expire.ONE_MINUTE, loader = {Loader.IDENTITY, Loader.BULK_IDENTITY}) + expiryTime = Expire.ONE_MINUTE, loader = {Loader.BULK_IDENTITY}) @SuppressWarnings("FutureReturnValueIgnored") public void getAll(AsyncLoadingCache cache, CacheContext context) { Set keys = context.firstMiddleLastKeys(); @@ -550,7 +550,7 @@ public void getAll(AsyncLoadingCache cache, CacheContext conte @Test(dataProvider = "caches") @CacheSpec(population = Population.FULL, mustExpiresWithAnyOf = { AFTER_ACCESS, AFTER_WRITE, VARIABLE }, - expiry = { CacheExpiry.DISABLED, CacheExpiry.WRITE, CacheExpiry.ACCESS }, + expiry = { CacheExpiry.DISABLED, CacheExpiry.CREATE, CacheExpiry.WRITE, CacheExpiry.ACCESS }, expireAfterAccess = {Expire.DISABLED, Expire.ONE_MINUTE}, expiryTime = Expire.ONE_MINUTE, expireAfterWrite = {Expire.DISABLED, Expire.ONE_MINUTE}) public void put_insert(AsyncLoadingCache cache, CacheContext context) { @@ -563,6 +563,32 @@ public void put_insert(AsyncLoadingCache cache, CacheContext c assertThat(cache, hasRemovalNotifications(context, count, RemovalCause.EXPIRED)); } + @Test(dataProvider = "caches") + @CacheSpec(population = Population.EMPTY, removalListener = Listener.CONSUMING, + mustExpiresWithAnyOf = { AFTER_ACCESS, AFTER_WRITE, VARIABLE }, + expiry = { CacheExpiry.DISABLED, CacheExpiry.CREATE, CacheExpiry.WRITE, CacheExpiry.ACCESS }, + expireAfterAccess = {Expire.DISABLED, Expire.ONE_MINUTE}, expiryTime = Expire.ONE_MINUTE, + expireAfterWrite = {Expire.DISABLED, Expire.ONE_MINUTE}) + @SuppressWarnings("FutureReturnValueIgnored") + public void put_insert_async(AsyncLoadingCache cache, CacheContext context) { + CompletableFuture future = new CompletableFuture(); + cache.put(context.absentKey(), future); + context.ticker().advance(2, TimeUnit.MINUTES); + cache.synchronous().cleanUp(); + + assertThat(cache, hasRemovalNotifications(context, 0, RemovalCause.EXPIRED)); + future.complete(context.absentValue()); + context.ticker().advance(30, TimeUnit.SECONDS); + assertThat(cache.getIfPresent(context.absentKey()), is(future)); + + context.ticker().advance(1, TimeUnit.MINUTES); + assertThat(cache.getIfPresent(context.absentKey()), is(nullValue())); + + cache.synchronous().cleanUp(); + assertThat(cache, hasRemovalNotifications(context, 1, RemovalCause.EXPIRED)); + verifyWriter(context, (verifier, writer) -> verifier.deletions(1, RemovalCause.EXPIRED)); + } + @Test(dataProvider = "caches") @CacheSpec(population = Population.FULL, expiryTime = Expire.ONE_MINUTE, mustExpiresWithAnyOf = { AFTER_ACCESS, AFTER_WRITE, VARIABLE }, @@ -593,7 +619,7 @@ public void put_replace(AsyncLoadingCache cache, CacheContext @Test(dataProvider = "caches") @CacheSpec(population = Population.FULL, expiryTime = Expire.ONE_MINUTE, mustExpiresWithAnyOf = { AFTER_ACCESS, AFTER_WRITE, VARIABLE }, - expiry = { CacheExpiry.DISABLED, CacheExpiry.WRITE, CacheExpiry.ACCESS }, + expiry = { CacheExpiry.DISABLED, CacheExpiry.CREATE, CacheExpiry.WRITE, CacheExpiry.ACCESS }, expireAfterAccess = {Expire.DISABLED, Expire.ONE_MINUTE}, expireAfterWrite = {Expire.DISABLED, Expire.ONE_MINUTE}) public void isEmpty(Map map, CacheContext context) { @@ -604,7 +630,7 @@ public void isEmpty(Map map, CacheContext context) { @Test(dataProvider = "caches") @CacheSpec(population = Population.FULL, expiryTime = Expire.ONE_MINUTE, mustExpiresWithAnyOf = { AFTER_ACCESS, AFTER_WRITE, VARIABLE }, - expiry = { CacheExpiry.DISABLED, CacheExpiry.WRITE, CacheExpiry.ACCESS }, + expiry = { CacheExpiry.DISABLED, CacheExpiry.CREATE, CacheExpiry.WRITE, CacheExpiry.ACCESS }, expireAfterAccess = {Expire.DISABLED, Expire.ONE_MINUTE}, expireAfterWrite = {Expire.DISABLED, Expire.ONE_MINUTE}) public void size(Map map, CacheContext context) { @@ -615,7 +641,7 @@ public void size(Map map, CacheContext context) { @Test(dataProvider = "caches") @CacheSpec(population = Population.FULL, expiryTime = Expire.ONE_MINUTE, mustExpiresWithAnyOf = { AFTER_ACCESS, AFTER_WRITE, VARIABLE }, - expiry = { CacheExpiry.DISABLED, CacheExpiry.WRITE, CacheExpiry.ACCESS }, + expiry = { CacheExpiry.DISABLED, CacheExpiry.CREATE, CacheExpiry.WRITE, CacheExpiry.ACCESS }, expireAfterAccess = {Expire.DISABLED, Expire.ONE_MINUTE}, expireAfterWrite = {Expire.DISABLED, Expire.ONE_MINUTE}) public void containsKey(Map map, CacheContext context) { @@ -626,7 +652,7 @@ public void containsKey(Map map, CacheContext context) { @Test(dataProvider = "caches") @CacheSpec(population = Population.FULL, expiryTime = Expire.ONE_MINUTE, mustExpiresWithAnyOf = { AFTER_ACCESS, AFTER_WRITE, VARIABLE }, - expiry = { CacheExpiry.DISABLED, CacheExpiry.WRITE, CacheExpiry.ACCESS }, + expiry = { CacheExpiry.DISABLED, CacheExpiry.CREATE, CacheExpiry.WRITE, CacheExpiry.ACCESS }, expireAfterAccess = {Expire.DISABLED, Expire.ONE_MINUTE}, expireAfterWrite = {Expire.DISABLED, Expire.ONE_MINUTE}) public void containsValue(Map map, CacheContext context) { @@ -637,7 +663,7 @@ public void containsValue(Map map, CacheContext context) { @Test(dataProvider = "caches") @CacheSpec(population = Population.FULL, expiryTime = Expire.ONE_MINUTE, mustExpiresWithAnyOf = { AFTER_ACCESS, AFTER_WRITE, VARIABLE }, - expiry = { CacheExpiry.DISABLED, CacheExpiry.WRITE, CacheExpiry.ACCESS }, + expiry = { CacheExpiry.DISABLED, CacheExpiry.CREATE, CacheExpiry.WRITE, CacheExpiry.ACCESS }, expireAfterAccess = {Expire.DISABLED, Expire.ONE_MINUTE}, expireAfterWrite = {Expire.DISABLED, Expire.ONE_MINUTE}) public void clear(Map map, CacheContext context) { @@ -653,7 +679,7 @@ public void clear(Map map, CacheContext context) { @CacheSpec(implementation = Implementation.Caffeine, keys = ReferenceType.STRONG, population = Population.FULL, expiryTime = Expire.ONE_MINUTE, mustExpiresWithAnyOf = { AFTER_ACCESS, AFTER_WRITE, VARIABLE }, - expiry = { CacheExpiry.DISABLED, CacheExpiry.WRITE, CacheExpiry.ACCESS }, + expiry = { CacheExpiry.DISABLED, CacheExpiry.CREATE, CacheExpiry.WRITE, CacheExpiry.ACCESS }, expireAfterAccess = {Expire.DISABLED, Expire.ONE_MINUTE}, expireAfterWrite = {Expire.DISABLED, Expire.ONE_MINUTE}, compute = Compute.SYNC, writer = Writer.EXCEPTIONAL, removalListener = Listener.REJECTING) @@ -672,7 +698,7 @@ public void clear_writerFails(Map map, CacheContext context) { @CacheSpec(implementation = Implementation.Caffeine, keys = ReferenceType.STRONG, population = Population.FULL, expiryTime = Expire.ONE_MINUTE, mustExpiresWithAnyOf = { AFTER_ACCESS, AFTER_WRITE, VARIABLE }, - expiry = { CacheExpiry.DISABLED, CacheExpiry.WRITE, CacheExpiry.ACCESS }, + expiry = { CacheExpiry.DISABLED, CacheExpiry.CREATE, CacheExpiry.WRITE, CacheExpiry.ACCESS }, expireAfterAccess = {Expire.DISABLED, Expire.ONE_MINUTE}, expireAfterWrite = {Expire.DISABLED, Expire.ONE_MINUTE}, compute = Compute.SYNC, writer = Writer.EXCEPTIONAL, removalListener = Listener.REJECTING) @@ -690,7 +716,7 @@ public void putIfAbsent_writerFails(Map map, CacheContext cont @Test(dataProvider = "caches") @CacheSpec(population = Population.FULL, expiryTime = Expire.ONE_MINUTE, mustExpiresWithAnyOf = { AFTER_ACCESS, AFTER_WRITE, VARIABLE }, - expiry = { CacheExpiry.DISABLED, CacheExpiry.WRITE, CacheExpiry.ACCESS }, + expiry = { CacheExpiry.DISABLED, CacheExpiry.CREATE, CacheExpiry.WRITE, CacheExpiry.ACCESS }, expireAfterAccess = {Expire.DISABLED, Expire.ONE_MINUTE}, expireAfterWrite = {Expire.DISABLED, Expire.ONE_MINUTE}) public void put_insert(Map map, CacheContext context) { @@ -735,7 +761,7 @@ public void put_replace(Map map, CacheContext context) { @CacheSpec(implementation = Implementation.Caffeine, keys = ReferenceType.STRONG, population = Population.FULL, expiryTime = Expire.ONE_MINUTE, mustExpiresWithAnyOf = { AFTER_ACCESS, AFTER_WRITE, VARIABLE }, - expiry = { CacheExpiry.DISABLED, CacheExpiry.WRITE, CacheExpiry.ACCESS }, + expiry = { CacheExpiry.DISABLED, CacheExpiry.CREATE, CacheExpiry.WRITE, CacheExpiry.ACCESS }, expireAfterAccess = {Expire.DISABLED, Expire.ONE_MINUTE}, expireAfterWrite = {Expire.DISABLED, Expire.ONE_MINUTE}, compute = Compute.SYNC, writer = Writer.EXCEPTIONAL, removalListener = Listener.REJECTING) @@ -753,7 +779,7 @@ public void put_writerFails(Map map, CacheContext context) { @Test(dataProvider = "caches") @CacheSpec(population = Population.FULL, expiryTime = Expire.ONE_MINUTE, mustExpiresWithAnyOf = { AFTER_ACCESS, AFTER_WRITE, VARIABLE }, - expiry = { CacheExpiry.DISABLED, CacheExpiry.WRITE, CacheExpiry.ACCESS }, + expiry = { CacheExpiry.DISABLED, CacheExpiry.CREATE, CacheExpiry.WRITE, CacheExpiry.ACCESS }, expireAfterAccess = {Expire.DISABLED, Expire.ONE_MINUTE}, expireAfterWrite = {Expire.DISABLED, Expire.ONE_MINUTE}) public void replace(Map map, CacheContext context) { @@ -791,7 +817,7 @@ public void replace_updated(Map map, CacheContext context) { @Test(dataProvider = "caches") @CacheSpec(population = Population.FULL, expiryTime = Expire.ONE_MINUTE, mustExpiresWithAnyOf = { AFTER_ACCESS, AFTER_WRITE, VARIABLE }, - expiry = { CacheExpiry.DISABLED, CacheExpiry.WRITE, CacheExpiry.ACCESS }, + expiry = { CacheExpiry.DISABLED, CacheExpiry.CREATE, CacheExpiry.WRITE, CacheExpiry.ACCESS }, expireAfterAccess = {Expire.DISABLED, Expire.ONE_MINUTE}, expireAfterWrite = {Expire.DISABLED, Expire.ONE_MINUTE}) public void replaceConditionally(Map map, CacheContext context) { @@ -831,7 +857,7 @@ public void replaceConditionally_updated(Map map, CacheContext @Test(dataProvider = "caches") @CacheSpec(population = Population.FULL, expiryTime = Expire.ONE_MINUTE, mustExpiresWithAnyOf = { AFTER_ACCESS, AFTER_WRITE, VARIABLE }, - expiry = { CacheExpiry.DISABLED, CacheExpiry.WRITE, CacheExpiry.ACCESS }, + expiry = { CacheExpiry.DISABLED, CacheExpiry.CREATE, CacheExpiry.WRITE, CacheExpiry.ACCESS }, expireAfterAccess = {Expire.DISABLED, Expire.ONE_MINUTE}, expireAfterWrite = {Expire.DISABLED, Expire.ONE_MINUTE}) public void remove(Map map, CacheContext context) { @@ -847,7 +873,7 @@ public void remove(Map map, CacheContext context) { @CacheSpec(implementation = Implementation.Caffeine, keys = ReferenceType.STRONG, population = Population.FULL, compute = Compute.SYNC, expiryTime = Expire.ONE_MINUTE, mustExpiresWithAnyOf = { AFTER_ACCESS, AFTER_WRITE, VARIABLE }, - expiry = { CacheExpiry.DISABLED, CacheExpiry.WRITE, CacheExpiry.ACCESS }, + expiry = { CacheExpiry.DISABLED, CacheExpiry.CREATE, CacheExpiry.WRITE, CacheExpiry.ACCESS }, expireAfterAccess = {Expire.DISABLED, Expire.ONE_MINUTE}, expireAfterWrite = {Expire.DISABLED, Expire.ONE_MINUTE}, writer = Writer.EXCEPTIONAL, removalListener = Listener.REJECTING) @@ -865,7 +891,7 @@ public void remove_writerFails(Map map, CacheContext context) @Test(dataProvider = "caches") @CacheSpec(population = Population.FULL, expiryTime = Expire.ONE_MINUTE, mustExpiresWithAnyOf = { AFTER_ACCESS, AFTER_WRITE, VARIABLE }, - expiry = { CacheExpiry.DISABLED, CacheExpiry.WRITE, CacheExpiry.ACCESS }, + expiry = { CacheExpiry.DISABLED, CacheExpiry.CREATE, CacheExpiry.WRITE, CacheExpiry.ACCESS }, expireAfterAccess = {Expire.DISABLED, Expire.ONE_MINUTE}, expireAfterWrite = {Expire.DISABLED, Expire.ONE_MINUTE}) public void removeConditionally(Map map, CacheContext context) { @@ -882,7 +908,7 @@ public void removeConditionally(Map map, CacheContext context) @CacheSpec(implementation = Implementation.Caffeine, keys = ReferenceType.STRONG, population = Population.FULL, expiryTime = Expire.ONE_MINUTE, mustExpiresWithAnyOf = { AFTER_ACCESS, AFTER_WRITE, VARIABLE }, - expiry = { CacheExpiry.DISABLED, CacheExpiry.WRITE, CacheExpiry.ACCESS }, + expiry = { CacheExpiry.DISABLED, CacheExpiry.CREATE, CacheExpiry.WRITE, CacheExpiry.ACCESS }, expireAfterAccess = {Expire.DISABLED, Expire.ONE_MINUTE}, expireAfterWrite = {Expire.DISABLED, Expire.ONE_MINUTE}, compute = Compute.SYNC, writer = Writer.EXCEPTIONAL, removalListener = Listener.REJECTING) @@ -901,7 +927,7 @@ public void removeConditionally_writerFails(Map map, CacheCont @Test(dataProvider = "caches") @CacheSpec(population = Population.FULL, expiryTime = Expire.ONE_MINUTE, mustExpiresWithAnyOf = { AFTER_ACCESS, AFTER_WRITE, VARIABLE }, - expiry = { CacheExpiry.DISABLED, CacheExpiry.WRITE, CacheExpiry.ACCESS }, + expiry = { CacheExpiry.DISABLED, CacheExpiry.CREATE, CacheExpiry.WRITE, CacheExpiry.ACCESS }, expireAfterAccess = {Expire.DISABLED, Expire.ONE_MINUTE}, expireAfterWrite = {Expire.DISABLED, Expire.ONE_MINUTE}) public void computeIfAbsent(Map map, CacheContext context) { @@ -919,7 +945,7 @@ public void computeIfAbsent(Map map, CacheContext context) { @CacheSpec(implementation = Implementation.Caffeine, keys = ReferenceType.STRONG, population = Population.FULL, expiryTime = Expire.ONE_MINUTE, mustExpiresWithAnyOf = { AFTER_ACCESS, AFTER_WRITE, VARIABLE }, - expiry = { CacheExpiry.DISABLED, CacheExpiry.WRITE, CacheExpiry.ACCESS }, + expiry = { CacheExpiry.DISABLED, CacheExpiry.CREATE, CacheExpiry.WRITE, CacheExpiry.ACCESS }, expireAfterAccess = {Expire.DISABLED, Expire.ONE_MINUTE}, expireAfterWrite = {Expire.DISABLED, Expire.ONE_MINUTE}, compute = Compute.SYNC, writer = Writer.EXCEPTIONAL, removalListener = Listener.REJECTING) @@ -938,7 +964,7 @@ public void computeIfAbsent_writerFails(Map map, CacheContext @Test(dataProvider = "caches") @CacheSpec(population = Population.FULL, expiryTime = Expire.ONE_MINUTE, mustExpiresWithAnyOf = { AFTER_ACCESS, AFTER_WRITE, VARIABLE }, - expiry = { CacheExpiry.DISABLED, CacheExpiry.WRITE, CacheExpiry.ACCESS }, + expiry = { CacheExpiry.DISABLED, CacheExpiry.CREATE, CacheExpiry.WRITE, CacheExpiry.ACCESS }, expireAfterAccess = {Expire.DISABLED, Expire.ONE_MINUTE}, expireAfterWrite = {Expire.DISABLED, Expire.ONE_MINUTE}) public void computeIfPresent(Map map, CacheContext context) { @@ -961,7 +987,7 @@ public void computeIfPresent(Map map, CacheContext context) { @CacheSpec(implementation = Implementation.Caffeine, keys = ReferenceType.STRONG, population = Population.FULL, expiryTime = Expire.ONE_MINUTE, mustExpiresWithAnyOf = { AFTER_ACCESS, AFTER_WRITE, VARIABLE }, - expiry = { CacheExpiry.DISABLED, CacheExpiry.WRITE, CacheExpiry.ACCESS }, + expiry = { CacheExpiry.DISABLED, CacheExpiry.CREATE, CacheExpiry.WRITE, CacheExpiry.ACCESS }, expireAfterAccess = {Expire.DISABLED, Expire.ONE_MINUTE}, expireAfterWrite = {Expire.DISABLED, Expire.ONE_MINUTE}, compute = Compute.SYNC, writer = Writer.EXCEPTIONAL, @@ -981,7 +1007,7 @@ public void computeIfPresent_writerFails(Map map, CacheContext @Test(dataProvider = "caches") @CacheSpec(population = Population.FULL, expiryTime = Expire.ONE_MINUTE, mustExpiresWithAnyOf = { AFTER_ACCESS, AFTER_WRITE, VARIABLE }, - expiry = { CacheExpiry.DISABLED, CacheExpiry.WRITE, CacheExpiry.ACCESS }, + expiry = { CacheExpiry.DISABLED, CacheExpiry.CREATE, CacheExpiry.WRITE, CacheExpiry.ACCESS }, expireAfterAccess = {Expire.DISABLED, Expire.ONE_MINUTE}, expireAfterWrite = {Expire.DISABLED, Expire.ONE_MINUTE}) public void compute(Map map, CacheContext context) { @@ -1002,7 +1028,7 @@ public void compute(Map map, CacheContext context) { @CacheSpec(implementation = Implementation.Caffeine, keys = ReferenceType.STRONG, population = Population.FULL, expiryTime = Expire.ONE_MINUTE, mustExpiresWithAnyOf = { AFTER_ACCESS, AFTER_WRITE, VARIABLE }, - expiry = { CacheExpiry.DISABLED, CacheExpiry.WRITE, CacheExpiry.ACCESS }, + expiry = { CacheExpiry.DISABLED, CacheExpiry.CREATE, CacheExpiry.WRITE, CacheExpiry.ACCESS }, expireAfterAccess = {Expire.DISABLED, Expire.ONE_MINUTE}, expireAfterWrite = {Expire.DISABLED, Expire.ONE_MINUTE}, compute = Compute.SYNC, writer = Writer.EXCEPTIONAL, removalListener = Listener.REJECTING) @@ -1021,7 +1047,7 @@ public void compute_writerFails(Map map, CacheContext context) @Test(dataProvider = "caches") @CacheSpec(population = Population.FULL, expiryTime = Expire.ONE_MINUTE, mustExpiresWithAnyOf = { AFTER_ACCESS, AFTER_WRITE, VARIABLE }, - expiry = { CacheExpiry.DISABLED, CacheExpiry.WRITE, CacheExpiry.ACCESS }, + expiry = { CacheExpiry.DISABLED, CacheExpiry.CREATE, CacheExpiry.WRITE, CacheExpiry.ACCESS }, expireAfterAccess = {Expire.DISABLED, Expire.ONE_MINUTE}, expireAfterWrite = {Expire.DISABLED, Expire.ONE_MINUTE}) public void merge(Map map, CacheContext context) { @@ -1041,7 +1067,7 @@ public void merge(Map map, CacheContext context) { @CacheSpec(implementation = Implementation.Caffeine, keys = ReferenceType.STRONG, population = Population.FULL, expiryTime = Expire.ONE_MINUTE, mustExpiresWithAnyOf = { AFTER_ACCESS, AFTER_WRITE, VARIABLE }, - expiry = { CacheExpiry.DISABLED, CacheExpiry.WRITE, CacheExpiry.ACCESS }, + expiry = { CacheExpiry.DISABLED, CacheExpiry.CREATE, CacheExpiry.WRITE, CacheExpiry.ACCESS }, expireAfterAccess = {Expire.DISABLED, Expire.ONE_MINUTE}, expireAfterWrite = {Expire.DISABLED, Expire.ONE_MINUTE}, compute = Compute.SYNC, writer = Writer.EXCEPTIONAL, removalListener = Listener.REJECTING) @@ -1061,7 +1087,7 @@ public void merge_writerFails(Map map, CacheContext context) { @Test(dataProvider = "caches") @CacheSpec(population = Population.FULL, expiryTime = Expire.ONE_MINUTE, mustExpiresWithAnyOf = { AFTER_ACCESS, AFTER_WRITE, VARIABLE }, - expiry = { CacheExpiry.DISABLED, CacheExpiry.WRITE, CacheExpiry.ACCESS }, + expiry = { CacheExpiry.DISABLED, CacheExpiry.CREATE, CacheExpiry.WRITE, CacheExpiry.ACCESS }, expireAfterAccess = {Expire.DISABLED, Expire.ONE_MINUTE}, expireAfterWrite = {Expire.DISABLED, Expire.ONE_MINUTE}) public void iterators(Map map, CacheContext context) { @@ -1077,7 +1103,7 @@ public void iterators(Map map, CacheContext context) { @CacheSpec(implementation = Implementation.Caffeine, population = Population.EMPTY, maximumSize = Maximum.FULL, weigher = CacheWeigher.COLLECTION, expiryTime = Expire.ONE_MINUTE, mustExpiresWithAnyOf = { AFTER_ACCESS, AFTER_WRITE, VARIABLE }, - expiry = { CacheExpiry.DISABLED, CacheExpiry.WRITE, CacheExpiry.ACCESS }, + expiry = { CacheExpiry.DISABLED, CacheExpiry.CREATE, CacheExpiry.WRITE, CacheExpiry.ACCESS }, expireAfterAccess = {Expire.DISABLED, Expire.ONE_MINUTE}, expireAfterWrite = {Expire.DISABLED, Expire.ONE_MINUTE}) public void putIfAbsent_weighted(Cache> cache, CacheContext context) { @@ -1092,7 +1118,7 @@ public void putIfAbsent_weighted(Cache> cache, CacheConte @CacheSpec(implementation = Implementation.Caffeine, population = Population.EMPTY, maximumSize = Maximum.FULL, weigher = CacheWeigher.COLLECTION, expiryTime = Expire.ONE_MINUTE, mustExpiresWithAnyOf = { AFTER_ACCESS, AFTER_WRITE, VARIABLE }, - expiry = { CacheExpiry.DISABLED, CacheExpiry.WRITE, CacheExpiry.ACCESS }, + expiry = { CacheExpiry.DISABLED, CacheExpiry.CREATE, CacheExpiry.WRITE, CacheExpiry.ACCESS }, expireAfterAccess = {Expire.DISABLED, Expire.ONE_MINUTE}, expireAfterWrite = {Expire.DISABLED, Expire.ONE_MINUTE}) public void put_weighted(Cache> cache, CacheContext context) { @@ -1107,7 +1133,7 @@ public void put_weighted(Cache> cache, CacheContext conte @CacheSpec(implementation = Implementation.Caffeine, population = Population.EMPTY, maximumSize = Maximum.FULL, weigher = CacheWeigher.COLLECTION, expiryTime = Expire.ONE_MINUTE, mustExpiresWithAnyOf = { AFTER_ACCESS, AFTER_WRITE, VARIABLE }, - expiry = { CacheExpiry.DISABLED, CacheExpiry.WRITE, CacheExpiry.ACCESS }, + expiry = { CacheExpiry.DISABLED, CacheExpiry.CREATE, CacheExpiry.WRITE, CacheExpiry.ACCESS }, expireAfterAccess = {Expire.DISABLED, Expire.ONE_MINUTE}, expireAfterWrite = {Expire.DISABLED, Expire.ONE_MINUTE}) public void computeIfAbsent_weighted(Cache> cache, CacheContext context) { @@ -1122,7 +1148,7 @@ public void computeIfAbsent_weighted(Cache> cache, CacheC @CacheSpec(implementation = Implementation.Caffeine, population = Population.EMPTY, maximumSize = Maximum.FULL, weigher = CacheWeigher.COLLECTION, expiryTime = Expire.ONE_MINUTE, mustExpiresWithAnyOf = { AFTER_ACCESS, AFTER_WRITE, VARIABLE }, - expiry = { CacheExpiry.DISABLED, CacheExpiry.WRITE, CacheExpiry.ACCESS }, + expiry = { CacheExpiry.DISABLED, CacheExpiry.CREATE, CacheExpiry.WRITE, CacheExpiry.ACCESS }, expireAfterAccess = {Expire.DISABLED, Expire.ONE_MINUTE}, expireAfterWrite = {Expire.DISABLED, Expire.ONE_MINUTE}) public void compute_weighted(Cache> cache, CacheContext context) { @@ -1137,7 +1163,7 @@ public void compute_weighted(Cache> cache, CacheContext c @CacheSpec(implementation = Implementation.Caffeine, population = Population.EMPTY, maximumSize = Maximum.FULL, weigher = CacheWeigher.COLLECTION, expiryTime = Expire.ONE_MINUTE, mustExpiresWithAnyOf = { AFTER_ACCESS, AFTER_WRITE, VARIABLE }, - expiry = { CacheExpiry.DISABLED, CacheExpiry.WRITE, CacheExpiry.ACCESS }, + expiry = { CacheExpiry.DISABLED, CacheExpiry.CREATE, CacheExpiry.WRITE, CacheExpiry.ACCESS }, expireAfterAccess = {Expire.DISABLED, Expire.ONE_MINUTE}, expireAfterWrite = {Expire.DISABLED, Expire.ONE_MINUTE}) public void merge_weighted(Cache> cache, CacheContext context) { diff --git a/caffeine/src/test/java/com/github/benmanes/caffeine/cache/ExpireAfterVarTest.java b/caffeine/src/test/java/com/github/benmanes/caffeine/cache/ExpireAfterVarTest.java index fc92235d7b..5c49f8349b 100644 --- a/caffeine/src/test/java/com/github/benmanes/caffeine/cache/ExpireAfterVarTest.java +++ b/caffeine/src/test/java/com/github/benmanes/caffeine/cache/ExpireAfterVarTest.java @@ -15,13 +15,18 @@ */ package com.github.benmanes.caffeine.cache; +import static com.github.benmanes.caffeine.cache.testing.CacheWriterVerifier.verifyWriter; +import static com.github.benmanes.caffeine.cache.testing.HasRemovalNotifications.hasRemovalNotifications; import static com.github.benmanes.caffeine.testing.IsEmptyMap.emptyMap; +import static com.github.benmanes.caffeine.testing.IsFutureValue.futureOf; import static java.util.concurrent.TimeUnit.NANOSECONDS; import static java.util.function.Function.identity; import static org.hamcrest.MatcherAssert.assertThat; import static org.hamcrest.Matchers.contains; import static org.hamcrest.Matchers.equalTo; import static org.hamcrest.Matchers.is; +import static org.hamcrest.Matchers.not; +import static org.hamcrest.Matchers.nullValue; import static org.mockito.ArgumentMatchers.any; import static org.mockito.ArgumentMatchers.anyInt; import static org.mockito.ArgumentMatchers.anyLong; @@ -34,6 +39,7 @@ import java.util.Optional; import java.util.OptionalLong; import java.util.Set; +import java.util.concurrent.CompletableFuture; import java.util.concurrent.TimeUnit; import org.testng.annotations.Listeners; @@ -51,6 +57,7 @@ import com.github.benmanes.caffeine.cache.testing.CacheSpec.Writer; import com.github.benmanes.caffeine.cache.testing.CacheValidationListener; import com.google.common.collect.ImmutableList; +import com.google.common.collect.ImmutableMap; import com.google.common.collect.Iterables; /** @@ -62,6 +69,123 @@ @Test(dataProviderClass = CacheProvider.class) public final class ExpireAfterVarTest { + /* ---------------- Create -------------- */ + + @Test(dataProvider = "caches") + @CacheSpec(population = Population.FULL, + expiryTime = Expire.ONE_MINUTE, expiry = CacheExpiry.CREATE) + public void put_replace(Cache cache, CacheContext context) { + context.ticker().advance(30, TimeUnit.SECONDS); + + cache.put(context.firstKey(), context.absentValue()); + cache.put(context.absentKey(), context.absentValue()); + context.consumedNotifications().clear(); // Ignore replacement notification + + context.ticker().advance(45, TimeUnit.SECONDS); + assertThat(cache.getIfPresent(context.firstKey()), is(nullValue())); + assertThat(cache.getIfPresent(context.middleKey()), is(nullValue())); + assertThat(cache.getIfPresent(context.absentKey()), is(context.absentValue())); + assertThat(cache.estimatedSize(), is(1L)); + + long count = context.initialSize(); + assertThat(cache, hasRemovalNotifications(context, count, RemovalCause.EXPIRED)); + verifyWriter(context, (verifier, writer) -> verifier.deletions(count, RemovalCause.EXPIRED)); + } + + @Test(dataProvider = "caches") + @CacheSpec(population = Population.FULL, + expiryTime = Expire.ONE_MINUTE, expiry = CacheExpiry.CREATE) + public void put_replace(AsyncLoadingCache cache, CacheContext context) { + CompletableFuture future = CompletableFuture.completedFuture(context.absentValue()); + context.ticker().advance(30, TimeUnit.SECONDS); + + cache.put(context.firstKey(), future); + cache.put(context.absentKey(), future); + context.consumedNotifications().clear(); // Ignore replacement notification + + context.ticker().advance(45, TimeUnit.SECONDS); + assertThat(cache.getIfPresent(context.firstKey()), is(nullValue())); + assertThat(cache.getIfPresent(context.middleKey()), is(nullValue())); + assertThat(cache.getIfPresent(context.absentKey()), is(futureOf(context.absentValue()))); + assertThat(cache.synchronous().estimatedSize(), is(1L)); + + long count = context.initialSize(); + assertThat(cache, hasRemovalNotifications(context, count, RemovalCause.EXPIRED)); + verifyWriter(context, (verifier, writer) -> verifier.deletions(count, RemovalCause.EXPIRED)); + } + + @Test(dataProvider = "caches") + @CacheSpec(population = Population.FULL, + expiryTime = Expire.ONE_MINUTE, expiry = CacheExpiry.CREATE) + public void put_replace(Map map, CacheContext context) { + context.ticker().advance(30, TimeUnit.SECONDS); + + assertThat(map.put(context.firstKey(), context.absentValue()), is(not(nullValue()))); + assertThat(map.put(context.absentKey(), context.absentValue()), is(nullValue())); + context.consumedNotifications().clear(); // Ignore replacement notification + + context.ticker().advance(45, TimeUnit.SECONDS); + assertThat(map.get(context.firstKey()), is(nullValue())); + assertThat(map.get(context.middleKey()), is(nullValue())); + assertThat(map.get(context.absentKey()), is(context.absentValue())); + assertThat(map.size(), is(1)); + + long count = context.initialSize(); + assertThat(map, hasRemovalNotifications(context, count, RemovalCause.EXPIRED)); + verifyWriter(context, (verifier, writer) -> verifier.deletions(count, RemovalCause.EXPIRED)); + } + + @Test(dataProvider = "caches") + @CacheSpec(population = Population.FULL, + expiryTime = Expire.ONE_MINUTE, expiry = CacheExpiry.CREATE) + public void putAll_replace(Cache cache, CacheContext context) { + context.ticker().advance(30, TimeUnit.SECONDS); + + cache.putAll(ImmutableMap.of( + context.firstKey(), context.absentValue(), + context.absentKey(), context.absentValue())); + context.consumedNotifications().clear(); // Ignore replacement notification + + context.ticker().advance(45, TimeUnit.SECONDS); + assertThat(cache.getIfPresent(context.firstKey()), is(nullValue())); + assertThat(cache.getIfPresent(context.middleKey()), is(nullValue())); + assertThat(cache.getIfPresent(context.absentKey()), is(context.absentValue())); + assertThat(cache.estimatedSize(), is(1L)); + + long count = context.initialSize(); + assertThat(cache, hasRemovalNotifications(context, count, RemovalCause.EXPIRED)); + verifyWriter(context, (verifier, writer) -> verifier.deletions(count, RemovalCause.EXPIRED)); + } + + @Test(dataProvider = "caches") + @CacheSpec(population = Population.FULL, + expiryTime = Expire.ONE_MINUTE, expiry = CacheExpiry.CREATE) + public void replace_updated(Map map, CacheContext context) { + context.ticker().advance(30, TimeUnit.SECONDS); + assertThat(map.replace(context.firstKey(), context.absentValue()), is(not(nullValue()))); + context.ticker().advance(30, TimeUnit.SECONDS); + + context.cleanUp(); + assertThat(map.size(), is(0)); + long count = context.initialSize(); + verifyWriter(context, (verifier, writer) -> verifier.deletions(count)); + } + + @Test(dataProvider = "caches") + @CacheSpec(population = Population.FULL, + expiryTime = Expire.ONE_MINUTE, expiry = CacheExpiry.CREATE) + public void replaceConditionally_updated(Map map, CacheContext context) { + Integer key = context.firstKey(); + context.ticker().advance(30, TimeUnit.SECONDS); + assertThat(map.replace(key, context.original().get(key), context.absentValue()), is(true)); + context.ticker().advance(30, TimeUnit.SECONDS); + + context.cleanUp(); + assertThat(map, is(emptyMap())); + long count = context.initialSize(); + verifyWriter(context, (verifier, writer) -> verifier.deletions(count)); + } + /* ---------------- Exceptional -------------- */ @CacheSpec(implementation = Implementation.Caffeine, diff --git a/caffeine/src/test/java/com/github/benmanes/caffeine/cache/testing/CacheSpec.java b/caffeine/src/test/java/com/github/benmanes/caffeine/cache/testing/CacheSpec.java index ecb6969a71..64df141dab 100644 --- a/caffeine/src/test/java/com/github/benmanes/caffeine/cache/testing/CacheSpec.java +++ b/caffeine/src/test/java/com/github/benmanes/caffeine/cache/testing/CacheSpec.java @@ -286,12 +286,10 @@ enum CacheExpiry { return mock; } }, - ACCESS { + CREATE { @Override public Expiry createExpiry(Expire expiryTime) { return ExpiryBuilder .expiringAfterCreate(expiryTime.timeNanos()) - .expiringAfterUpdate(expiryTime.timeNanos()) - .expiringAfterRead(expiryTime.timeNanos()) .build(); } }, @@ -302,6 +300,15 @@ enum CacheExpiry { .expiringAfterUpdate(expiryTime.timeNanos()) .build(); } + }, + ACCESS { + @Override public Expiry createExpiry(Expire expiryTime) { + return ExpiryBuilder + .expiringAfterCreate(expiryTime.timeNanos()) + .expiringAfterUpdate(expiryTime.timeNanos()) + .expiringAfterRead(expiryTime.timeNanos()) + .build(); + } }; public abstract Expiry createExpiry(Expire expiryTime); diff --git a/gradle/dependencies.gradle b/gradle/dependencies.gradle index 4fdc6defda..5f344319c5 100644 --- a/gradle/dependencies.gradle +++ b/gradle/dependencies.gradle @@ -25,14 +25,14 @@ */ ext { versions = [ - akka: '2.5.1', + akka: '2.5.2', commons_compress: '1.14', commons_lang3: '3.5', config: '1.3.1', error_prone_annotations: '2.0.19', fastutil: '7.2.0', flip_tables: '1.0.2', - guava: '21.0', + guava: '22.0', javapoet: '1.9.0', jcache: '1.0.0', jsr305: '3.0.2', @@ -69,10 +69,10 @@ ext { ohc: '0.6.1', rapidoid: '5.3.4', slf4j: '1.7.25', - tcache: '1.0.1', + tcache: '1.0.3', ] plugin_versions = [ - buildscan: '1.7.1', + buildscan: '1.7.3', buildscan_recipes: '0.2.0', checkstyle: '7.7', coveralls: '2.8.1', @@ -129,7 +129,7 @@ ext { exclude group: 'org.hamcrest' }, osgi_compile: [ - 'org.apache.felix:org.apache.felix.framework:5.6.2', + 'org.apache.felix:org.apache.felix.framework:5.6.4', "org.ops4j.pax.exam:pax-exam-junit4:${test_versions.pax_exam}", ], osgi_runtime: [ diff --git a/guava/src/test/java/com/google/common/cache/LocalCacheMapComputeTest.java b/guava/src/test/java/com/google/common/cache/LocalCacheMapComputeTest.java new file mode 100644 index 0000000000..620503754f --- /dev/null +++ b/guava/src/test/java/com/google/common/cache/LocalCacheMapComputeTest.java @@ -0,0 +1,86 @@ +/* + * Copyright (C) 2017 The Guava Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.google.common.cache; + +import static com.google.common.truth.Truth.assertThat; + +import java.util.concurrent.TimeUnit; +import java.util.function.IntConsumer; +import java.util.stream.IntStream; + +import com.github.benmanes.caffeine.cache.Caffeine; +import com.github.benmanes.caffeine.guava.CaffeinatedGuava; + +import junit.framework.TestCase; + +/** + * Test Java8 map.compute in concurrent cache context. + */ +public class LocalCacheMapComputeTest extends TestCase { + final int count = 10000; + final String delimiter = "-"; + final String key = "key"; + Cache cache; + + // helper + private static void doParallelCacheOp(int count, IntConsumer consumer) { + IntStream.range(0, count).parallel().forEach(consumer); + } + + @Override + public void setUp() throws Exception { + super.setUp(); + this.cache = CaffeinatedGuava.build(Caffeine.newBuilder() + .expireAfterAccess(500000, TimeUnit.MILLISECONDS) + .maximumSize(count)); + } + + public void testComputeIfAbsent() { + // simultaneous insertion for same key, expect 1 winner + doParallelCacheOp(count, n -> { + cache.asMap().computeIfAbsent(key, k -> "value" + n); + }); + assertEquals(1, cache.size()); + } + + public void testComputeIfPresent() { + cache.put(key, "1"); + // simultaneous update for same key, expect count successful updates + doParallelCacheOp(count, n -> { + cache.asMap().computeIfPresent(key, (k, v) -> v + delimiter + n); + }); + assertEquals(1, cache.size()); + assertThat(cache.getIfPresent(key).split(delimiter)).hasLength(count + 1); + } + + public void testUpdates() { + cache.put(key, "1"); + // simultaneous update for same key, some null, some non-null + doParallelCacheOp(count, n -> { + cache.asMap().compute(key, (k, v) -> n % 2 == 0 ? v + delimiter + n : null); + }); + assertTrue(1 >= cache.size()); + } + + public void testCompute() { + cache.put(key, "1"); + // simultaneous deletion + doParallelCacheOp(count, n -> { + cache.asMap().compute(key, (k, v) -> null); + }); + assertEquals(0, cache.size()); + } +}