diff --git a/README.md b/README.md index ef6a26c452..9a5e7a93c8 100644 --- a/README.md +++ b/README.md @@ -123,7 +123,7 @@ Snapshots of the development version are available in [solr]: https://issues.apache.org/jira/browse/SOLR-8241 [infinispan]: http://infinispan.org/docs/stable/user_guide/user_guide.html#eviction_strategy [neo4j]: https://github.com/neo4j/neo4j -[ohc]: https://github.com/snazy/ohc/issues/34 +[ohc]: https://github.com/snazy/ohc [go-tinylfu]: https://github.com/dgryski/go-tinylfu [mango-cache]: https://github.com/goburrow/cache [ratpack]: https://github.com/ratpack/ratpack diff --git a/caffeine/src/javaPoet/java/com/github/benmanes/caffeine/cache/NodeSelectorCode.java b/caffeine/src/javaPoet/java/com/github/benmanes/caffeine/cache/NodeSelectorCode.java index 026d81cc64..bf8f5b5a2c 100644 --- a/caffeine/src/javaPoet/java/com/github/benmanes/caffeine/cache/NodeSelectorCode.java +++ b/caffeine/src/javaPoet/java/com/github/benmanes/caffeine/cache/NodeSelectorCode.java @@ -49,11 +49,23 @@ private NodeSelectorCode values() { } private NodeSelectorCode expires() { - block.beginControlFlow("if (builder.expiresAfterAccess() || builder.expiresVariable())") - .addStatement("sb.append('A')") - .endControlFlow() - .beginControlFlow("if (builder.expiresAfterWrite())") - .addStatement("sb.append('W')") + block + .beginControlFlow("if (builder.expiresVariable())") + .beginControlFlow("if (builder.refreshes())") + .addStatement("sb.append('A')") + .beginControlFlow("if (builder.evicts())") + .addStatement("sb.append('W')") + .endControlFlow() + .nextControlFlow("else") + .addStatement("sb.append('W')") + .endControlFlow() + .nextControlFlow("else") + .beginControlFlow("if (builder.expiresAfterAccess())") + .addStatement("sb.append('A')") + .endControlFlow() + .beginControlFlow("if (builder.expiresAfterWrite())") + .addStatement("sb.append('W')") + .endControlFlow() .endControlFlow() .beginControlFlow("if (builder.refreshes())") .addStatement("sb.append('R')") diff --git a/caffeine/src/javaPoet/java/com/github/benmanes/caffeine/cache/local/AddExpireAfterAccess.java b/caffeine/src/javaPoet/java/com/github/benmanes/caffeine/cache/local/AddExpireAfterAccess.java index 1ac8265916..307421fbb7 100644 --- a/caffeine/src/javaPoet/java/com/github/benmanes/caffeine/cache/local/AddExpireAfterAccess.java +++ b/caffeine/src/javaPoet/java/com/github/benmanes/caffeine/cache/local/AddExpireAfterAccess.java @@ -67,7 +67,7 @@ private void variableExpiration() { .returns(boolean.class) .build()); - context.constructor.addStatement("this.expiry = builder.getExpiry()"); + context.constructor.addStatement("this.expiry = builder.getExpiry(isAsync)"); context.cache.addField(FieldSpec.builder(EXPIRY, "expiry", privateFinalModifiers).build()); context.cache.addMethod(MethodSpec.methodBuilder("expiry") .addModifiers(protectedFinalModifiers) diff --git a/caffeine/src/javaPoet/java/com/github/benmanes/caffeine/cache/node/AddDeques.java b/caffeine/src/javaPoet/java/com/github/benmanes/caffeine/cache/node/AddDeques.java index fc3b507cc3..a175eea3d2 100644 --- a/caffeine/src/javaPoet/java/com/github/benmanes/caffeine/cache/node/AddDeques.java +++ b/caffeine/src/javaPoet/java/com/github/benmanes/caffeine/cache/node/AddDeques.java @@ -17,8 +17,6 @@ import static com.github.benmanes.caffeine.cache.Specifications.NODE; -import javax.lang.model.element.Modifier; - import com.github.benmanes.caffeine.cache.Feature; /** @@ -49,7 +47,7 @@ protected void execute() { /** Adds a simple field, accessor, and mutator for the variable. */ private void addFieldAndGetter(String varName) { - context.nodeSubtype.addField(NODE, varName, Modifier.PRIVATE) + context.nodeSubtype.addField(NODE, varName) .addMethod(newGetter(Strength.STRONG, NODE, varName, Visibility.IMMEDIATE)) .addMethod(newSetter(NODE, varName, Visibility.IMMEDIATE)); } diff --git a/caffeine/src/javaPoet/java/com/github/benmanes/caffeine/cache/node/AddExpiration.java b/caffeine/src/javaPoet/java/com/github/benmanes/caffeine/cache/node/AddExpiration.java index 360c1b59eb..285e311739 100644 --- a/caffeine/src/javaPoet/java/com/github/benmanes/caffeine/cache/node/AddExpiration.java +++ b/caffeine/src/javaPoet/java/com/github/benmanes/caffeine/cache/node/AddExpiration.java @@ -15,9 +15,11 @@ */ package com.github.benmanes.caffeine.cache.node; +import static com.github.benmanes.caffeine.cache.Specifications.NODE; import static com.github.benmanes.caffeine.cache.Specifications.UNSAFE_ACCESS; import static com.github.benmanes.caffeine.cache.Specifications.newFieldOffset; import static com.github.benmanes.caffeine.cache.Specifications.offsetName; +import static org.apache.commons.lang3.StringUtils.capitalize; import javax.lang.model.element.Modifier; @@ -39,19 +41,70 @@ protected boolean applies() { @Override protected void execute() { + addVariableExpiration(); addAccessExpiration(); addWriteExpiration(); addRefreshExpiration(); } + private void addVariableExpiration() { + if (context.generateFeatures.contains(Feature.EXPIRE_ACCESS)) { + addLink("previousInVariableOrder", "previousInAccessOrder"); + addLink("nextInVariableOrder", "nextInAccessOrder"); + addVariableTime("accessTime"); + } else if (context.generateFeatures.contains(Feature.EXPIRE_WRITE)) { + addLink("previousInVariableOrder", "previousInWriteOrder"); + addLink("nextInVariableOrder", "nextInWriteOrder"); + addVariableTime("writeTime"); + } + if (context.parentFeatures.contains(Feature.EXPIRE_ACCESS) + && context.parentFeatures.contains(Feature.EXPIRE_WRITE) + && context.generateFeatures.contains(Feature.REFRESH_WRITE)) { + addLink("previousInVariableOrder", "previousInWriteOrder"); + addLink("nextInVariableOrder", "nextInWriteOrder"); + addVariableTime("accessTime"); + } + } + + private void addLink(String method, String varName) { + MethodSpec getter = MethodSpec.methodBuilder("get" + capitalize(method)) + .addModifiers(Modifier.PUBLIC) + .addStatement("return $N", varName) + .returns(NODE) + .build(); + MethodSpec setter = MethodSpec.methodBuilder("set" + capitalize(method)) + .addModifiers(Modifier.PUBLIC) + .addParameter(NODE, varName) + .addStatement("this.$N = $N", varName, varName) + .build(); + context.nodeSubtype + .addMethod(getter) + .addMethod(setter); + } + + private void addVariableTime(String varName) { + MethodSpec getter = MethodSpec.methodBuilder("getVariableTime") + .addModifiers(Modifier.PUBLIC) + .addStatement("return $N", varName) + .returns(long.class) + .build(); + MethodSpec setter = MethodSpec.methodBuilder("setVariableTime") + .addModifiers(Modifier.PUBLIC) + .addParameter(long.class, varName) + .addStatement("this.$N = $N", varName, varName) + .build(); + context.nodeSubtype + .addMethod(getter) + .addMethod(setter); + } + private void addAccessExpiration() { if (!context.generateFeatures.contains(Feature.EXPIRE_ACCESS)) { return; } context.nodeSubtype.addField(newFieldOffset(context.className, "accessTime")) - .addField(long.class, "accessTime", Modifier.PRIVATE, Modifier.VOLATILE) - .addMethod(newGetter(Strength.STRONG, TypeName.LONG, - "accessTime", Visibility.LAZY)) + .addField(long.class, "accessTime", Modifier.VOLATILE) + .addMethod(newGetter(Strength.STRONG, TypeName.LONG, "accessTime", Visibility.LAZY)) .addMethod(newSetter(TypeName.LONG, "accessTime", Visibility.LAZY)); addTimeConstructorAssignment(context.constructorByKey, "accessTime"); addTimeConstructorAssignment(context.constructorByKeyRef, "accessTime"); @@ -61,7 +114,7 @@ private void addWriteExpiration() { if (!Feature.useWriteTime(context.parentFeatures) && Feature.useWriteTime(context.generateFeatures)) { context.nodeSubtype.addField(newFieldOffset(context.className, "writeTime")) - .addField(long.class, "writeTime", Modifier.PRIVATE, Modifier.VOLATILE) + .addField(long.class, "writeTime", Modifier.VOLATILE) .addMethod(newGetter(Strength.STRONG, TypeName.LONG, "writeTime", Visibility.LAZY)) .addMethod(newSetter(TypeName.LONG, "writeTime", Visibility.LAZY)); addTimeConstructorAssignment(context.constructorByKey, "writeTime"); diff --git a/caffeine/src/javaPoet/java/com/github/benmanes/caffeine/cache/node/AddKey.java b/caffeine/src/javaPoet/java/com/github/benmanes/caffeine/cache/node/AddKey.java index a8a63b33ea..d4169eee06 100644 --- a/caffeine/src/javaPoet/java/com/github/benmanes/caffeine/cache/node/AddKey.java +++ b/caffeine/src/javaPoet/java/com/github/benmanes/caffeine/cache/node/AddKey.java @@ -55,10 +55,9 @@ protected void execute() { } private FieldSpec newKeyField() { - Modifier[] modifiers = { Modifier.PRIVATE, Modifier.VOLATILE }; FieldSpec.Builder fieldSpec = isStrongKeys() - ? FieldSpec.builder(kTypeVar, "key", modifiers) - : FieldSpec.builder(keyReferenceType(), "key", modifiers); + ? FieldSpec.builder(kTypeVar, "key", Modifier.VOLATILE) + : FieldSpec.builder(keyReferenceType(), "key", Modifier.VOLATILE); return fieldSpec.build(); } diff --git a/caffeine/src/javaPoet/java/com/github/benmanes/caffeine/cache/node/AddMaximum.java b/caffeine/src/javaPoet/java/com/github/benmanes/caffeine/cache/node/AddMaximum.java index 8055aa95d8..7cac4b1fea 100644 --- a/caffeine/src/javaPoet/java/com/github/benmanes/caffeine/cache/node/AddMaximum.java +++ b/caffeine/src/javaPoet/java/com/github/benmanes/caffeine/cache/node/AddMaximum.java @@ -40,7 +40,7 @@ protected void execute() { } private void addQueueFlag() { - context.nodeSubtype.addField(int.class, "queueType", Modifier.PRIVATE); + context.nodeSubtype.addField(int.class, "queueType"); context.nodeSubtype.addMethod(MethodSpec.methodBuilder("getQueueType") .addModifiers(Modifier.PUBLIC, Modifier.FINAL) .returns(int.class) @@ -57,13 +57,13 @@ private void addWeight() { if (!context.generateFeatures.contains(Feature.MAXIMUM_WEIGHT)) { return; } - context.nodeSubtype.addField(int.class, "weight", Modifier.PRIVATE) + context.nodeSubtype.addField(int.class, "weight") .addMethod(newGetter(Strength.STRONG, TypeName.INT, "weight", Visibility.IMMEDIATE)) .addMethod(newSetter(TypeName.INT, "weight", Visibility.IMMEDIATE)); context.constructorByKey.addStatement("this.$N = $N", "weight", "weight"); context.constructorByKeyRef.addStatement("this.$N = $N", "weight", "weight"); - context.nodeSubtype.addField(int.class, "policyWeight", Modifier.PRIVATE) + context.nodeSubtype.addField(int.class, "policyWeight") .addMethod(newGetter(Strength.STRONG, TypeName.INT, "policyWeight", Visibility.IMMEDIATE)) .addMethod(newSetter(TypeName.INT, "policyWeight", Visibility.IMMEDIATE)); } diff --git a/caffeine/src/javaPoet/java/com/github/benmanes/caffeine/cache/node/AddToString.java b/caffeine/src/javaPoet/java/com/github/benmanes/caffeine/cache/node/AddToString.java index c53a8a2c4c..5d33e3766b 100644 --- a/caffeine/src/javaPoet/java/com/github/benmanes/caffeine/cache/node/AddToString.java +++ b/caffeine/src/javaPoet/java/com/github/benmanes/caffeine/cache/node/AddToString.java @@ -34,11 +34,12 @@ protected boolean applies() { @Override protected void execute() { String statement = "return String.format(\"%s=[key=%s, value=%s, weight=%d, queueType=%,d, " - + "accessTimeNS=%,d, \"\n+ \"writeTimeNS=%,d, prevInAccess=%s, nextInAccess=%s, " - + "prevInWrite=%s, nextInWrite=%s]\",\ngetClass().getSimpleName(), getKey(), getValue(), " - + "getWeight(), getQueueType(), \ngetAccessTime(), getWriteTime(), " - + "getPreviousInAccessOrder() != null,\ngetNextInAccessOrder() != null, " - + "getPreviousInWriteOrder() != null,\ngetNextInWriteOrder() != null)"; + + "accessTimeNS=%,d, \"\n+ \"writeTimeNS=%,d, varTimeNs=%,d, prevInAccess=%s, " + + "nextInAccess=%s, prevInWrite=%s, \"\n+ \"nextInWrite=%s]\", getClass().getSimpleName(), " + + "getKey(), getValue(), getWeight(), \ngetQueueType(), getAccessTime(), getWriteTime(), " + + "getVariableTime(), \ngetPreviousInAccessOrder() != null, " + + "getNextInAccessOrder() != null, \ngetPreviousInWriteOrder() != null, " + + "getNextInWriteOrder() != null)"; context.nodeSubtype.addMethod(MethodSpec.methodBuilder("toString") .addModifiers(Modifier.PUBLIC, Modifier.FINAL) diff --git a/caffeine/src/javaPoet/java/com/github/benmanes/caffeine/cache/node/AddValue.java b/caffeine/src/javaPoet/java/com/github/benmanes/caffeine/cache/node/AddValue.java index 4123afccd8..76f8652b83 100644 --- a/caffeine/src/javaPoet/java/com/github/benmanes/caffeine/cache/node/AddValue.java +++ b/caffeine/src/javaPoet/java/com/github/benmanes/caffeine/cache/node/AddValue.java @@ -61,10 +61,9 @@ protected void execute() { } private FieldSpec newValueField() { - Modifier[] modifiers = { Modifier.PRIVATE, Modifier.VOLATILE }; FieldSpec.Builder fieldSpec = isStrongValues() - ? FieldSpec.builder(vTypeVar, "value", modifiers) - : FieldSpec.builder(valueReferenceType(), "value", modifiers); + ? FieldSpec.builder(vTypeVar, "value", Modifier.VOLATILE) + : FieldSpec.builder(valueReferenceType(), "value", Modifier.VOLATILE); return fieldSpec.build(); } diff --git a/caffeine/src/jmh/java/com/github/benmanes/caffeine/cache/TimerWheelBenchmark.java b/caffeine/src/jmh/java/com/github/benmanes/caffeine/cache/TimerWheelBenchmark.java index adab1b81e2..7ab850c147 100644 --- a/caffeine/src/jmh/java/com/github/benmanes/caffeine/cache/TimerWheelBenchmark.java +++ b/caffeine/src/jmh/java/com/github/benmanes/caffeine/cache/TimerWheelBenchmark.java @@ -56,7 +56,9 @@ public void setup() { timerWheel = new TimerWheel<>(new MockCache()); for (int i = 0; i < SIZE; i++) { times[i] = ThreadLocalRandom.current().nextLong(UPPERBOUND); + timerWheel.schedule(new Timer(times[i])); } + timerWheel.schedule(timer); } @Benchmark @@ -66,44 +68,44 @@ public void findBucket(ThreadState threadState) { @Benchmark public void reschedule(ThreadState threadState) { - timer.setAccessTime(times[threadState.index++ & MASK]); + timer.setVariableTime(times[threadState.index++ & MASK]); timerWheel.reschedule(timer); } @Benchmark public void expire(ThreadState threadState) { - long accessTime = times[threadState.index++ & MASK]; - timer.setAccessTime(accessTime); - timerWheel.nanos = accessTime - DELTA; + long time = times[threadState.index++ & MASK]; + timer.setVariableTime(time); + timerWheel.nanos = (time - DELTA); + timerWheel.advance(time); timerWheel.schedule(timer); - timerWheel.advance(accessTime); } - private static final class Timer implements Node { + static final class Timer implements Node { Node prev; Node next; - long accessTime; + long time; - Timer(long accessTime) { - setAccessTime(accessTime); + Timer(long time) { + setVariableTime(time); } - @Override public long getAccessTime() { - return accessTime; + @Override public long getVariableTime() { + return time; } - @Override public void setAccessTime(long accessTime) { - this.accessTime = accessTime; + @Override public void setVariableTime(long time) { + this.time = time; } - @Override public Node getPreviousInAccessOrder() { + @Override public Node getPreviousInVariableOrder() { return prev; } - @Override public void setPreviousInAccessOrder(@Nullable Node prev) { + @Override public void setPreviousInVariableOrder(@Nullable Node prev) { this.prev = prev; } - @Override public Node getNextInAccessOrder() { + @Override public Node getNextInVariableOrder() { return next; } - @Override public void setNextInAccessOrder(@Nullable Node next) { + @Override public void setNextInVariableOrder(@Nullable Node next) { this.next = next; } @@ -120,7 +122,7 @@ private static final class Timer implements Node { @Override public void die() {} } - private static final class MockCache extends BoundedLocalCache { + static final class MockCache extends BoundedLocalCache { @SuppressWarnings({"unchecked", "rawtypes"}) protected MockCache() { diff --git a/caffeine/src/main/java/com/github/benmanes/caffeine/cache/Async.java b/caffeine/src/main/java/com/github/benmanes/caffeine/cache/Async.java index 7f9efc30f5..a09e8baf57 100644 --- a/caffeine/src/main/java/com/github/benmanes/caffeine/cache/Async.java +++ b/caffeine/src/main/java/com/github/benmanes/caffeine/cache/Async.java @@ -32,6 +32,7 @@ * @author ben.manes@gmail.com (Ben Manes) */ final class Async { + static final long MAXIMUM_EXPIRY = (Long.MAX_VALUE >> 1); // 150 years private Async() {} @@ -111,4 +112,56 @@ Object writeReplace() { return delegate; } } + + /** + * An expiry for asynchronous computations. When the value is being loaded this expiry returns + * {@code Long.MAX_VALUE} to indicate that the entry should not be evicted due to an expiry + * constraint. If the value is computed successfully the entry must be reinserted so that the + * expiration is updated and the expiration timeouts reflect the value once present. The value + * maximum range is reserved to coordinate the asynchronous life cycle. + */ + static final class AsyncExpiry implements Expiry>, Serializable { + private static final long serialVersionUID = 1L; + + final Expiry delegate; + + AsyncExpiry(Expiry delegate) { + this.delegate = requireNonNull(delegate); + } + + @Override + public long expireAfterCreate(K key, CompletableFuture future, long currentTime) { + if (isReady(future)) { + long duration = delegate.expireAfterCreate(key, future.join(), currentTime); + return Math.min(duration, MAXIMUM_EXPIRY); + } + return Long.MAX_VALUE; + } + + @Override + public long expireAfterUpdate(K key, CompletableFuture future, + long currentTime, long currentDuration) { + if (isReady(future)) { + long duration = (currentDuration > MAXIMUM_EXPIRY) + ? delegate.expireAfterCreate(key, future.join(), currentTime) + : delegate.expireAfterUpdate(key, future.join(), currentDuration, currentTime); + return Math.min(duration, MAXIMUM_EXPIRY); + } + return currentDuration; + } + + @Override + public long expireAfterRead(K key, CompletableFuture future, + long currentTime, long currentDuration) { + if (isReady(future) && (currentDuration > MAXIMUM_EXPIRY)) { + long duration = delegate.expireAfterRead(key, future.join(), currentDuration, currentTime); + return Math.min(duration, MAXIMUM_EXPIRY); + } + return currentDuration; + } + + Object writeReplace() { + return delegate; + } + } } 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 5c32d99fe7..dcd4533534 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 @@ -730,7 +730,7 @@ boolean hasExpired(Node node, long now) { } return (expiresAfterAccess() && (now - node.getAccessTime() >= expiresAfterAccessNanos())) || (expiresAfterWrite() && (now - node.getWriteTime() >= expiresAfterWriteNanos())) - || (expiresVariable() && (now > node.getAccessTime())); + || (expiresVariable() && (now - node.getVariableTime() >= 0)); } /** @@ -763,11 +763,14 @@ boolean evictEntry(Node node, RemovalCause cause, long now) { boolean expired = false; if (expiresAfterAccess()) { long expirationTime = now - expiresAfterAccessNanos(); - expired |= n.getAccessTime() <= expirationTime; + expired |= (n.getAccessTime() <= expirationTime); } if (expiresAfterWrite()) { long expirationTime = now - expiresAfterWriteNanos(); - expired |= n.getWriteTime() <= expirationTime; + expired |= (n.getWriteTime() <= expirationTime); + } + if (expiresVariable()) { + expired |= (n.getVariableTime() <= now); } if (!expired) { resurrect[0] = true; @@ -935,10 +938,10 @@ void expireAfterCreate(Node node, K key, V value, long now) { if (expiresVariable()) { if ((key != null) && (value != null)) { long duration = expiry().expireAfterCreate(key, value, now); - node.setAccessTime(now + duration); + node.setVariableTime(now + duration); } } else { - node.setAccessTime(now); + setAccessTime(node, now); } } @@ -953,12 +956,12 @@ void expireAfterCreate(Node node, K key, V value, long now) { void expireAfterUpdate(Node node, K key, V value, long now) { if (expiresVariable()) { if ((key != null) && (value != null)) { - long currentDuration = Math.min(1, node.getAccessTime() - now); + long currentDuration = Math.max(1, node.getVariableTime() - now); long duration = expiry().expireAfterUpdate(key, value, now, currentDuration); - node.setAccessTime(now + duration); + node.setVariableTime(now + duration); } } else { - node.setAccessTime(now); + setAccessTime(node, now); } } @@ -973,11 +976,23 @@ void expireAfterUpdate(Node node, K key, V value, long now) { void expireAfterRead(Node node, K key, V value, long now) { if (expiresVariable()) { if ((key != null) && (value != null)) { - long currentDuration = Math.min(1, node.getAccessTime() - now); + long currentDuration = Math.max(1, node.getVariableTime() - now); long duration = expiry().expireAfterRead(key, value, now, currentDuration); - node.setAccessTime(now + duration); + node.setVariableTime(now + duration); } } else { + setAccessTime(node, now); + } + } + + void setWriteTime(Node node, long now) { + if (expiresAfterWrite() || refreshAfterWrite()) { + node.setWriteTime(now); + } + } + + void setAccessTime(Node node, long now) { + if (expiresAfterAccess()) { node.setAccessTime(now); } } @@ -991,8 +1006,8 @@ void expireAfterRead(Node node, K key, V value, long now) { */ void afterWrite(@Nullable Node node, Runnable task, long now) { if (node != null) { - node.setAccessTime(now); - node.setWriteTime(now); + setAccessTime(node, now); + setWriteTime(node, now); } if (buffersWrites()) { for (int i = 0; i < WRITE_BUFFER_RETRIES; i++) { @@ -1324,12 +1339,13 @@ public void run() { if (isComputingAsync(node)) { CompletableFuture future = (CompletableFuture) node.getValue(); if (future != null) { - node.setAccessTime(Long.MAX_VALUE); - node.setWriteTime(Long.MAX_VALUE); + node.setVariableTime(Long.MAX_VALUE); + setAccessTime(node, Long.MAX_VALUE); + setWriteTime(node, Long.MAX_VALUE); future.thenRun(() -> { long now = expirationTicker().read(); - node.setAccessTime(now); - node.setWriteTime(now); + setAccessTime(node, now); + setWriteTime(node, now); }); } } @@ -1699,7 +1715,7 @@ V put(K key, V value, boolean notifyWriter, boolean onlyIfAbsent) { if (onlyIfAbsent) { expireAfterRead(prior, key, value, now); } else { - prior.setWriteTime(now); + setWriteTime(prior, now); } afterRead(prior, now, /* recordHit */ false); } @@ -1865,13 +1881,13 @@ public V replace(K key, V value) { return n; } + expireAfterUpdate(n, key, value, now); if (value != oldValue[0]) { - expireAfterUpdate(n, key, value, now); writer.write(nodeKey[0], value); } n.setValue(value, valueReferenceQueue()); - n.setWriteTime(now); n.setWeight(weight); + setWriteTime(n, now); return n; } }); @@ -1920,13 +1936,13 @@ public boolean replace(K key, V oldValue, V newValue) { return n; } + expireAfterUpdate(n, key, newValue, now); if (newValue != prevValue[0]) { - expireAfterUpdate(n, key, newValue, now); writer.write(key, newValue); } n.setValue(newValue, valueReferenceQueue()); n.setWeight(weight); - n.setWriteTime(now); + setWriteTime(n, now); replaced[0] = true; } return n; @@ -2044,7 +2060,7 @@ V doComputeIfAbsent(K key, Object keyRef, expireAfterCreate(n, key, newValue[0], now); n.setValue(newValue[0], valueReferenceQueue()); n.setWeight(weight[1]); - n.setWriteTime(now); + setWriteTime(n, now); return n; } }); @@ -2208,7 +2224,7 @@ V remap(K key, Object keyRef, BiFunction rema } n.setValue(newValue[0], valueReferenceQueue()); n.setWeight(weight[1]); - n.setWriteTime(now); + setWriteTime(n, now); return n; } }); diff --git a/caffeine/src/main/java/com/github/benmanes/caffeine/cache/Caffeine.java b/caffeine/src/main/java/com/github/benmanes/caffeine/cache/Caffeine.java index 0d25482af2..9768ac7d52 100644 --- a/caffeine/src/main/java/com/github/benmanes/caffeine/cache/Caffeine.java +++ b/caffeine/src/main/java/com/github/benmanes/caffeine/cache/Caffeine.java @@ -33,6 +33,7 @@ import javax.annotation.Nonnegative; import javax.annotation.Nonnull; +import com.github.benmanes.caffeine.cache.Async.AsyncExpiry; import com.github.benmanes.caffeine.cache.Async.AsyncRemovalListener; import com.github.benmanes.caffeine.cache.Async.AsyncWeigher; import com.github.benmanes.caffeine.cache.stats.CacheStats; @@ -401,7 +402,7 @@ Weigher getWeigher(boolean isAsync) { Weigher delegate = isWeighted() && (weigher != Weigher.singletonWeigher()) ? Weigher.boundedWeigher((Weigher) weigher) : Weigher.singletonWeigher(); - return (Weigher) (isAsync ? new AsyncWeigher<>(delegate) : delegate); + return isAsync ? (Weigher) new AsyncWeigher<>(delegate) : delegate; } /** @@ -591,7 +592,7 @@ boolean expiresAfterAccess() { * @throws IllegalStateException if expiration was already set */ @Nonnull - /* public */ Caffeine expireAfter( + public Caffeine expireAfter( @Nonnull Expiry expiry) { requireNonNull(expiry); requireState(this.expiry == null, "Expiry was already set to %s", this.expiry); @@ -611,8 +612,9 @@ boolean expiresVariable() { } @SuppressWarnings("unchecked") - Expiry getExpiry() { - return (expiry == null) ? Expiry.eternalExpiry() : Expiry.boundedExpiry((Expiry) expiry); + Expiry getExpiry(boolean isAsync) { + Expiry delegate = (expiry == null) ? Expiry.eternalExpiry() : (Expiry) expiry; + return isAsync ? (Expiry) new AsyncExpiry<>(delegate) : delegate; } /** @@ -673,7 +675,9 @@ public Caffeine ticker(@Nonnull Ticker ticker) { @Nonnull Ticker getTicker() { - return expiresAfterAccess() || expiresAfterWrite() || refreshes() || isRecordingStats() + boolean useTicker = expiresVariable() || expiresAfterAccess() + || expiresAfterWrite() || refreshes() || isRecordingStats(); + return useTicker ? (ticker == null) ? Ticker.systemTicker() : ticker : Ticker.disabledTicker(); } @@ -822,6 +826,7 @@ boolean isBounded() { || (maximumWeight != UNSET_INT) || (expireAfterAccessNanos != UNSET_INT) || (expireAfterWriteNanos != UNSET_INT) + || (expiry != null) || (keyStrength != null) || (valueStrength != null); } diff --git a/caffeine/src/main/java/com/github/benmanes/caffeine/cache/Expiry.java b/caffeine/src/main/java/com/github/benmanes/caffeine/cache/Expiry.java index eb7feae97b..ad558996f1 100644 --- a/caffeine/src/main/java/com/github/benmanes/caffeine/cache/Expiry.java +++ b/caffeine/src/main/java/com/github/benmanes/caffeine/cache/Expiry.java @@ -15,11 +15,6 @@ */ package com.github.benmanes.caffeine.cache; -import static java.util.Objects.requireNonNull; - -import java.io.Serializable; - -import javax.annotation.Nonnull; import javax.annotation.concurrent.ThreadSafe; /** @@ -29,7 +24,7 @@ * @author ben.manes@gmail.com (Ben Manes) */ @ThreadSafe -interface Expiry { +public interface Expiry { /** * Specifies that the entry should be automatically removed from the cache once the duration has @@ -82,19 +77,6 @@ interface Expiry { static Expiry eternalExpiry() { return (Expiry) EternalExpiry.INSTANCE; } - - /** - * Returns an expiry where the minimum duration is non-negative. - * - * @param delegate the expiry to calculating the expiration time with - * @param the type of keys - * @param the type of values - * @return a weigher that enforces that the weight is non-negative - */ - @Nonnull - static Expiry boundedExpiry(@Nonnull Expiry delegate) { - return new BoundedExpiry<>(delegate); - } } enum EternalExpiry implements Expiry { @@ -115,35 +97,3 @@ public long expireAfterRead(Object key, Object value, long currentTime, long cur return Long.MAX_VALUE; } } - -final class BoundedExpiry implements Expiry, Serializable { - static final long serialVersionUID = 1; - final Expiry delegate; - - BoundedExpiry(Expiry delegate) { - this.delegate = requireNonNull(delegate); - } - - @Override - public long expireAfterCreate(K key, V value, long currentTime) { - long duration = delegate.expireAfterCreate(key, value, currentTime); - return (duration < 0) ? 0 : duration; - } - - @Override - public long expireAfterUpdate(K key, V value, long currentTime, long currentDuration) { - long duration = delegate.expireAfterUpdate(key, value, currentTime, currentDuration); - return (duration < 0) ? 0 : duration; - } - - @Override - public long expireAfterRead(K key, V value, long currentTime, long currentDuration) { - long duration = delegate.expireAfterUpdate(key, value, currentTime, currentDuration); - return (duration < 0) ? 0 : duration; - } - - Object writeReplace() { - return delegate; - } -} - diff --git a/caffeine/src/main/java/com/github/benmanes/caffeine/cache/Node.java b/caffeine/src/main/java/com/github/benmanes/caffeine/cache/Node.java index f76c5ae176..737344ff16 100644 --- a/caffeine/src/main/java/com/github/benmanes/caffeine/cache/Node.java +++ b/caffeine/src/main/java/com/github/benmanes/caffeine/cache/Node.java @@ -118,6 +118,39 @@ default void setPolicyWeight(int weight) {} @GuardedBy("this") void die(); + /* ---------------- Variable order -------------- */ + + /** Returns the time that this entry was last accessed, in ns. */ + default long getVariableTime() { + return 0L; + } + + /** + * Sets the variable expiration time in nanoseconds. This update may be set lazily and rely on the + * memory fence when the lock is released. + */ + default void setVariableTime(long time) {} + + @GuardedBy("evictionLock") + default @Nullable Node getPreviousInVariableOrder() { + return null; + } + + @GuardedBy("evictionLock") + default void setPreviousInVariableOrder(@Nullable Node prev) { + throw new UnsupportedOperationException(); + } + + @GuardedBy("evictionLock") + default @Nullable Node getNextInVariableOrder() { + return null; + } + + @GuardedBy("evictionLock") + default void setNextInVariableOrder(@Nullable Node prev) { + throw new UnsupportedOperationException(); + } + /* ---------------- Access order -------------- */ int EDEN = 0; @@ -172,7 +205,7 @@ default void setAccessTime(long time) {} @Override @GuardedBy("evictionLock") - default Node getPreviousInAccessOrder() { + default @Nullable Node getPreviousInAccessOrder() { return null; } @@ -184,7 +217,7 @@ default void setPreviousInAccessOrder(@Nullable Node prev) { @Override @GuardedBy("evictionLock") - default Node getNextInAccessOrder() { + default @Nullable Node getNextInAccessOrder() { return null; } @@ -217,7 +250,7 @@ default boolean casWriteTime(long expect, long update) { @Override @GuardedBy("evictionLock") - default Node getPreviousInWriteOrder() { + default @Nullable Node getPreviousInWriteOrder() { return null; } @@ -229,7 +262,7 @@ default void setPreviousInWriteOrder(@Nullable Node prev) { @Override @GuardedBy("evictionLock") - default Node getNextInWriteOrder() { + default @Nullable Node getNextInWriteOrder() { return null; } diff --git a/caffeine/src/main/java/com/github/benmanes/caffeine/cache/TimerWheel.java b/caffeine/src/main/java/com/github/benmanes/caffeine/cache/TimerWheel.java index af79652396..94a57d8877 100644 --- a/caffeine/src/main/java/com/github/benmanes/caffeine/cache/TimerWheel.java +++ b/caffeine/src/main/java/com/github/benmanes/caffeine/cache/TimerWheel.java @@ -87,15 +87,19 @@ final class TimerWheel { */ public void advance(long currentTimeNanos) { long previousTimeNanos = nanos; - nanos = currentTimeNanos; - - for (int i = 0; i < SHIFT.length; i++) { - int prevTicks = (int) (previousTimeNanos >>> SHIFT[i]); - int currentTicks = (int) (currentTimeNanos >>> SHIFT[i]); - if ((currentTicks - prevTicks) <= 0) { - break; + try { + nanos = currentTimeNanos; + for (int i = 0; i < SHIFT.length; i++) { + long previousTicks = (previousTimeNanos >> SHIFT[i]); + long currentTicks = (currentTimeNanos >> SHIFT[i]); + if ((currentTicks - previousTicks) <= 0) { + break; + } + expire(i, previousTicks, currentTicks, previousTimeNanos, currentTimeNanos); } - expire(i, previousTimeNanos, currentTimeNanos); + } catch (Throwable t) { + nanos = previousTimeNanos; + throw t; } } @@ -103,10 +107,13 @@ public void advance(long currentTimeNanos) { * Expires entries or reschedules into the proper bucket if still active. * * @param index the wheel being operated on + * @param previousTicks the previous number of ticks + * @param currentTicks the current number of ticks * @param previousTimeNanos the previous time, in nanoseconds * @param currentTimeNanos the current time, in nanoseconds */ - void expire(int index, long previousTimeNanos, long currentTimeNanos) { + void expire(int index, long previousTicks, long currentTicks, + long previousTimeNanos, long currentTimeNanos) { Node[] timerWheel = wheel[index]; int start, end; @@ -114,32 +121,38 @@ void expire(int index, long previousTimeNanos, long currentTimeNanos) { end = timerWheel.length; start = 0; } else { - long previousTicks = (previousTimeNanos >>> SHIFT[index]); - long currentTicks = (currentTimeNanos >>> SHIFT[index]) + SPANS[index]; long mask = SPANS[index] - 1; - start = (int) (previousTicks & mask); - end = start + 1 + (int) (currentTicks & mask); + end = 1 + (int) (currentTicks & mask); } int mask = timerWheel.length - 1; for (int i = start; i < end; i++) { Node sentinel = timerWheel[(i & mask)]; - Node node = sentinel.getNextInAccessOrder(); - sentinel.setPreviousInAccessOrder(sentinel); - sentinel.setNextInAccessOrder(sentinel); + Node prev = sentinel.getPreviousInVariableOrder(); + Node node = sentinel.getNextInVariableOrder(); + sentinel.setPreviousInVariableOrder(sentinel); + sentinel.setNextInVariableOrder(sentinel); while (node != sentinel) { - Node next = node.getNextInAccessOrder(); - node.setPreviousInAccessOrder(null); - node.setNextInAccessOrder(null); - - if ((node.getAccessTime() > currentTimeNanos) - || !cache.evictEntry(node, RemovalCause.EXPIRED, nanos)) { - Node newSentinel = findBucket(node.getAccessTime()); - link(newSentinel, node); + Node next = node.getNextInVariableOrder(); + node.setPreviousInVariableOrder(null); + node.setNextInVariableOrder(null); + + try { + if (((node.getVariableTime() - currentTimeNanos) > 0) + || !cache.evictEntry(node, RemovalCause.EXPIRED, nanos)) { + Node newSentinel = findBucket(node.getVariableTime()); + link(newSentinel, node); + } + node = next; + } catch (Throwable t) { + node.setPreviousInVariableOrder(sentinel.getPreviousInVariableOrder()); + node.setNextInVariableOrder(next); + sentinel.getPreviousInVariableOrder().setNextInVariableOrder(node); + sentinel.setPreviousInVariableOrder(prev); + throw t; } - node = next; } } } @@ -150,7 +163,7 @@ void expire(int index, long previousTimeNanos, long currentTimeNanos) { * @param node the entry in the cache */ public void schedule(Node node) { - Node sentinel = findBucket(node.getAccessTime()); + Node sentinel = findBucket(node.getVariableTime()); link(sentinel, node); } @@ -160,7 +173,7 @@ public void schedule(Node node) { * @param node the entry in the cache */ public void reschedule(Node node) { - if (node.getNextInAccessOrder() != null) { + if (node.getNextInVariableOrder() != null) { unlink(node); schedule(node); } @@ -173,8 +186,8 @@ public void reschedule(Node node) { */ public void deschedule(Node node) { unlink(node); - node.setNextInAccessOrder(null); - node.setPreviousInAccessOrder(null); + node.setNextInVariableOrder(null); + node.setPreviousInVariableOrder(null); } /** @@ -187,7 +200,7 @@ Node findBucket(long time) { long duration = time - nanos; for (int i = 0; i < wheel.length; i++) { if (duration < SPANS[i + 1]) { - int ticks = (int) (time >>> SHIFT[i]); + int ticks = (int) (time >> SHIFT[i]); int index = ticks & (wheel[i].length - 1); return wheel[i][index]; } @@ -196,29 +209,27 @@ Node findBucket(long time) { // Add to the last timer bucket int lastWheel = wheel.length - 1; int buckets = wheel[lastWheel].length - 1; - int ticks = (int) (nanos >>> SHIFT[lastWheel]) - 1; + int ticks = (int) (nanos >> SHIFT[lastWheel]) - 1; int index = (ticks & buckets); return wheel[lastWheel][index]; } /** Adds the entry at the tail of the bucket's list. */ void link(Node sentinel, Node node) { - node.setPreviousInAccessOrder(sentinel.getPreviousInAccessOrder()); - node.setNextInAccessOrder(sentinel); + node.setPreviousInVariableOrder(sentinel.getPreviousInVariableOrder()); + node.setNextInVariableOrder(sentinel); - sentinel.getPreviousInAccessOrder().setNextInAccessOrder(node); - sentinel.setPreviousInAccessOrder(node); + sentinel.getPreviousInVariableOrder().setNextInVariableOrder(node); + sentinel.setPreviousInVariableOrder(node); } /** Removes the entry from its bucket, if scheduled. */ void unlink(Node node) { - Node prev = node.getPreviousInAccessOrder(); - Node next = node.getNextInAccessOrder(); + Node prev = node.getPreviousInVariableOrder(); + Node next = node.getNextInVariableOrder(); if (next != null) { - next.setPreviousInAccessOrder(prev); - } - if (prev != null) { - prev.setNextInAccessOrder(next); + next.setPreviousInVariableOrder(prev); + prev.setNextInVariableOrder(next); } } @@ -229,8 +240,8 @@ public String toString() { Map buckets = new TreeMap<>(); for (int j = 0; j < wheel[i].length; j++) { int events = 0; - for (Node node = wheel[i][j].getNextInAccessOrder(); - node != wheel[i][j]; node = node.getNextInAccessOrder()) { + for (Node node = wheel[i][j].getNextInVariableOrder(); + node != wheel[i][j]; node = node.getNextInVariableOrder()) { events++; } if (events > 0) { @@ -251,16 +262,16 @@ static final class Sentinel implements Node { prev = next = this; } - @Override public Node getPreviousInAccessOrder() { + @Override public Node getPreviousInVariableOrder() { return prev; } - @Override public void setPreviousInAccessOrder(@Nullable Node prev) { + @Override public void setPreviousInVariableOrder(@Nullable Node prev) { this.prev = prev; } - @Override public Node getNextInAccessOrder() { + @Override public Node getNextInVariableOrder() { return next; } - @Override public void setNextInAccessOrder(@Nullable Node next) { + @Override public void setNextInVariableOrder(@Nullable Node next) { this.next = next; } diff --git a/caffeine/src/test/java/com/github/benmanes/caffeine/cache/BoundedLocalCacheTest.java b/caffeine/src/test/java/com/github/benmanes/caffeine/cache/BoundedLocalCacheTest.java index 6139158109..be5d3647d4 100644 --- a/caffeine/src/test/java/com/github/benmanes/caffeine/cache/BoundedLocalCacheTest.java +++ b/caffeine/src/test/java/com/github/benmanes/caffeine/cache/BoundedLocalCacheTest.java @@ -51,6 +51,7 @@ import com.github.benmanes.caffeine.cache.testing.CacheProvider; import com.github.benmanes.caffeine.cache.testing.CacheSpec; import com.github.benmanes.caffeine.cache.testing.CacheSpec.CacheExecutor; +import com.github.benmanes.caffeine.cache.testing.CacheSpec.CacheExpiry; import com.github.benmanes.caffeine.cache.testing.CacheSpec.CacheWeigher; import com.github.benmanes.caffeine.cache.testing.CacheSpec.Compute; import com.github.benmanes.caffeine.cache.testing.CacheSpec.ExecutorFailure; @@ -136,7 +137,7 @@ public void scheduleDrainBuffers_rejected(Cache cache, CacheCo @Test public void putWeighted_noOverflow() { Cache cache = Caffeine.newBuilder() - .executor(CacheExecutor.DIRECT.get()) + .executor(CacheExecutor.DIRECT.create()) .weigher(CacheWeigher.MAX_VALUE) .maximumWeight(Long.MAX_VALUE) .build(); @@ -444,7 +445,7 @@ public void exceedsMaximumBufferSize_onWrite(Cache cache, Cach @CacheSpec(compute = Compute.SYNC, implementation = Implementation.Caffeine, population = Population.EMPTY, maximumSize = Maximum.FULL, weigher = CacheWeigher.DEFAULT, expireAfterAccess = Expire.DISABLED, expireAfterWrite = Expire.DISABLED, - keys = ReferenceType.STRONG, values = ReferenceType.STRONG) + expiry = CacheExpiry.DISABLED, keys = ReferenceType.STRONG, values = ReferenceType.STRONG) public void fastpath(Cache cache, CacheContext context) { BoundedLocalCache localCache = asBoundedLocalCache(cache); assertThat(localCache.skipReadBuffer(), is(true)); 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 7322559761..b0efae5ad8 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 @@ -15,6 +15,9 @@ */ package com.github.benmanes.caffeine.cache; +import static com.github.benmanes.caffeine.cache.testing.CacheSpec.Expiration.AFTER_ACCESS; +import static com.github.benmanes.caffeine.cache.testing.CacheSpec.Expiration.AFTER_WRITE; +import static com.github.benmanes.caffeine.cache.testing.CacheSpec.Expiration.VARIABLE; 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.IsFutureValue.futureOf; @@ -37,6 +40,7 @@ import com.github.benmanes.caffeine.cache.testing.CacheContext; import com.github.benmanes.caffeine.cache.testing.CacheProvider; import com.github.benmanes.caffeine.cache.testing.CacheSpec; +import com.github.benmanes.caffeine.cache.testing.CacheSpec.CacheExpiry; import com.github.benmanes.caffeine.cache.testing.CacheSpec.CacheWeigher; import com.github.benmanes.caffeine.cache.testing.CacheSpec.Compute; import com.github.benmanes.caffeine.cache.testing.CacheSpec.ExecutorFailure; @@ -57,7 +61,7 @@ import com.google.common.collect.Maps; /** - * The test cases for caches that support the expire after read or expire after write policy. + * The test cases for caches that support an expiration policy. * * @author ben.manes@gmail.com (Ben Manes) */ @@ -66,8 +70,11 @@ public final class ExpirationTest { @Test(dataProvider = "caches") - @CacheSpec(requiresExpiration = true, expireAfterAccess = {Expire.DISABLED, Expire.IMMEDIATELY}, - expireAfterWrite = {Expire.DISABLED, Expire.IMMEDIATELY}, population = Population.EMPTY) + @CacheSpec(mustExpiresWithAnyOf = { AFTER_ACCESS, AFTER_WRITE, VARIABLE }, + expiry = { CacheExpiry.DISABLED, CacheExpiry.WRITE, CacheExpiry.ACCESS }, + expireAfterAccess = {Expire.DISABLED, Expire.IMMEDIATELY}, + expireAfterWrite = {Expire.DISABLED, Expire.IMMEDIATELY}, + expiryTime = Expire.IMMEDIATELY, population = Population.EMPTY) public void expire_zero(Cache cache, CacheContext context) { cache.put(context.absentKey(), context.absentValue()); if (context.isZeroWeighted() && context.isGuava()) { @@ -75,6 +82,7 @@ public void expire_zero(Cache cache, CacheContext context) { assertThat(cache.estimatedSize(), is(1L)); assertThat(cache, hasRemovalNotifications(context, 0, RemovalCause.EXPIRED)); } else { + runVariableExpiration(context); assertThat(cache.estimatedSize(), is(0L)); assertThat(cache, hasRemovalNotifications(context, 1, RemovalCause.EXPIRED)); verifyWriter(context, (verifier, writer) -> { @@ -87,8 +95,10 @@ public void expire_zero(Cache cache, CacheContext context) { @Test(dataProvider = "caches", expectedExceptions = DeleteException.class) @CacheSpec(implementation = Implementation.Caffeine, keys = ReferenceType.STRONG, - population = Population.FULL, writer = Writer.EXCEPTIONAL, requiresExpiration = true, - expireAfterAccess = {Expire.DISABLED, Expire.ONE_MINUTE}, + population = Population.FULL, writer = Writer.EXCEPTIONAL, + mustExpiresWithAnyOf = { AFTER_ACCESS, AFTER_WRITE, VARIABLE }, + expiry = { CacheExpiry.DISABLED, 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) public void getIfPresent_writerFails(Cache cache, CacheContext context) { @@ -104,7 +114,9 @@ public void getIfPresent_writerFails(Cache cache, CacheContext @Test(dataProvider = "caches", expectedExceptions = DeleteException.class) @CacheSpec(implementation = Implementation.Caffeine, keys = ReferenceType.STRONG, - population = Population.FULL, requiresExpiration = true, + population = Population.FULL, expiryTime = Expire.ONE_MINUTE, + mustExpiresWithAnyOf = { AFTER_ACCESS, AFTER_WRITE, VARIABLE }, + expiry = { CacheExpiry.DISABLED, 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) @@ -120,13 +132,16 @@ public void get_writerFails(Cache cache, CacheContext context) } @Test(dataProvider = "caches") - @CacheSpec(population = Population.FULL, requiresExpiration = true, + @CacheSpec(population = Population.FULL, expiryTime = Expire.ONE_MINUTE, + mustExpiresWithAnyOf = { AFTER_ACCESS, AFTER_WRITE, VARIABLE }, + expiry = { CacheExpiry.DISABLED, CacheExpiry.WRITE, CacheExpiry.ACCESS }, expireAfterAccess = {Expire.DISABLED, Expire.ONE_MINUTE}, expireAfterWrite = {Expire.DISABLED, Expire.ONE_MINUTE}) public void put_insert(Cache cache, CacheContext context) { context.ticker().advance(1, TimeUnit.MINUTES); cache.put(context.firstKey(), context.absentValue()); + runVariableExpiration(context); long count = context.initialSize(); assertThat(cache.estimatedSize(), is(1L)); assertThat(cache, hasRemovalNotifications(context, count, RemovalCause.EXPIRED)); @@ -134,7 +149,9 @@ public void put_insert(Cache cache, CacheContext context) { } @Test(dataProvider = "caches") - @CacheSpec(population = Population.FULL, requiresExpiration = true, + @CacheSpec(population = Population.FULL, expiryTime = Expire.ONE_MINUTE, + mustExpiresWithAnyOf = { AFTER_ACCESS, AFTER_WRITE, VARIABLE }, + expiry = { CacheExpiry.DISABLED, CacheExpiry.WRITE, CacheExpiry.ACCESS }, expireAfterAccess = {Expire.DISABLED, Expire.ONE_MINUTE}, expireAfterWrite = {Expire.DISABLED, Expire.ONE_MINUTE}) public void put_replace(Cache cache, CacheContext context) { @@ -161,7 +178,9 @@ public void put_replace(Cache cache, CacheContext context) { @Test(dataProvider = "caches", expectedExceptions = DeleteException.class) @CacheSpec(implementation = Implementation.Caffeine, keys = ReferenceType.STRONG, - population = Population.FULL, requiresExpiration = true, + population = Population.FULL, expiryTime = Expire.ONE_MINUTE, + mustExpiresWithAnyOf = { AFTER_ACCESS, AFTER_WRITE, VARIABLE }, + expiry = { CacheExpiry.DISABLED, 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) @@ -177,7 +196,9 @@ public void put_writerFails(Cache cache, CacheContext context) } @Test(dataProvider = "caches") - @CacheSpec(population = Population.FULL, requiresExpiration = true, + @CacheSpec(population = Population.FULL, expiryTime = Expire.ONE_MINUTE, + mustExpiresWithAnyOf = { AFTER_ACCESS, AFTER_WRITE, VARIABLE }, + expiry = { CacheExpiry.DISABLED, CacheExpiry.WRITE, CacheExpiry.ACCESS }, expireAfterAccess = {Expire.DISABLED, Expire.ONE_MINUTE}, expireAfterWrite = {Expire.DISABLED, Expire.ONE_MINUTE}) public void putAll_insert(Cache cache, CacheContext context) { @@ -192,7 +213,9 @@ public void putAll_insert(Cache cache, CacheContext context) { } @Test(dataProvider = "caches") - @CacheSpec(population = Population.FULL, requiresExpiration = true, + @CacheSpec(population = Population.FULL, expiryTime = Expire.ONE_MINUTE, + mustExpiresWithAnyOf = { AFTER_ACCESS, AFTER_WRITE, VARIABLE }, + expiry = { CacheExpiry.DISABLED, CacheExpiry.WRITE, CacheExpiry.ACCESS }, expireAfterAccess = {Expire.DISABLED, Expire.ONE_MINUTE}, expireAfterWrite = {Expire.DISABLED, Expire.ONE_MINUTE}) public void putAll_replace(Cache cache, CacheContext context) { @@ -220,7 +243,9 @@ public void putAll_replace(Cache cache, CacheContext context) @Test(dataProvider = "caches", expectedExceptions = DeleteException.class) @CacheSpec(implementation = Implementation.Caffeine, keys = ReferenceType.STRONG, - population = Population.FULL, requiresExpiration = true, + population = Population.FULL, expiryTime = Expire.ONE_MINUTE, + mustExpiresWithAnyOf = { AFTER_ACCESS, AFTER_WRITE, VARIABLE }, + expiry = { CacheExpiry.DISABLED, 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) @@ -236,7 +261,9 @@ public void putAll_writerFails(Cache cache, CacheContext conte } @Test(dataProvider = "caches") - @CacheSpec(population = Population.FULL, requiresExpiration = true, + @CacheSpec(population = Population.FULL, expiryTime = Expire.ONE_MINUTE, + mustExpiresWithAnyOf = { AFTER_ACCESS, AFTER_WRITE, VARIABLE }, + expiry = { CacheExpiry.DISABLED, CacheExpiry.WRITE, CacheExpiry.ACCESS }, expireAfterAccess = {Expire.DISABLED, Expire.ONE_MINUTE}, expireAfterWrite = {Expire.DISABLED, Expire.ONE_MINUTE}) public void invalidate(Cache cache, CacheContext context) { @@ -250,7 +277,9 @@ public void invalidate(Cache cache, CacheContext context) { @Test(dataProvider = "caches", expectedExceptions = DeleteException.class) @CacheSpec(implementation = Implementation.Caffeine, keys = ReferenceType.STRONG, - population = Population.FULL, requiresExpiration = true, + population = Population.FULL, expiryTime = Expire.ONE_MINUTE, + mustExpiresWithAnyOf = { AFTER_ACCESS, AFTER_WRITE, VARIABLE }, + expiry = { CacheExpiry.DISABLED, 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) @@ -266,7 +295,9 @@ public void invalidate_writerFails(Cache cache, CacheContext c } @Test(dataProvider = "caches") - @CacheSpec(population = Population.FULL, requiresExpiration = true, + @CacheSpec(population = Population.FULL, expiryTime = Expire.ONE_MINUTE, + mustExpiresWithAnyOf = { AFTER_ACCESS, AFTER_WRITE, VARIABLE }, + expiry = { CacheExpiry.DISABLED, CacheExpiry.WRITE, CacheExpiry.ACCESS }, expireAfterAccess = {Expire.DISABLED, Expire.ONE_MINUTE}, expireAfterWrite = {Expire.DISABLED, Expire.ONE_MINUTE}) public void invalidateAll(Cache cache, CacheContext context) { @@ -280,7 +311,9 @@ public void invalidateAll(Cache cache, CacheContext context) { @Test(dataProvider = "caches", expectedExceptions = DeleteException.class) @CacheSpec(implementation = Implementation.Caffeine, keys = ReferenceType.STRONG, - population = Population.FULL, requiresExpiration = true, + population = Population.FULL, expiryTime = Expire.ONE_MINUTE, + mustExpiresWithAnyOf = { AFTER_ACCESS, AFTER_WRITE, VARIABLE }, + expiry = { CacheExpiry.DISABLED, 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) @@ -296,7 +329,9 @@ public void invalidateAll_writerFails(Cache cache, CacheContex } @Test(dataProvider = "caches") - @CacheSpec(population = Population.FULL, requiresExpiration = true, + @CacheSpec(population = Population.FULL, expiryTime = Expire.ONE_MINUTE, + mustExpiresWithAnyOf = { AFTER_ACCESS, AFTER_WRITE, VARIABLE }, + expiry = { CacheExpiry.DISABLED, CacheExpiry.WRITE, CacheExpiry.ACCESS }, expireAfterAccess = {Expire.DISABLED, Expire.ONE_MINUTE}, expireAfterWrite = {Expire.DISABLED, Expire.ONE_MINUTE}) public void invalidateAll_full(Cache cache, CacheContext context) { @@ -310,7 +345,9 @@ public void invalidateAll_full(Cache cache, CacheContext conte @Test(dataProvider = "caches", expectedExceptions = DeleteException.class) @CacheSpec(implementation = Implementation.Caffeine, keys = ReferenceType.STRONG, - population = Population.FULL, requiresExpiration = true, + population = Population.FULL, expiryTime = Expire.ONE_MINUTE, + mustExpiresWithAnyOf = { AFTER_ACCESS, AFTER_WRITE, VARIABLE }, + expiry = { CacheExpiry.DISABLED, 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) @@ -327,8 +364,11 @@ public void invalidateAll_full_writerFails(Cache cache, CacheC @Test(dataProvider = "caches") @CacheSpec(population = { Population.SINGLETON, Population.PARTIAL, Population.FULL }, - requiresExpiration = true, expireAfterAccess = {Expire.DISABLED, Expire.ONE_MINUTE}, - expireAfterWrite = {Expire.DISABLED, Expire.ONE_MINUTE}) + mustExpiresWithAnyOf = { AFTER_ACCESS, AFTER_WRITE, VARIABLE }, + expiry = { CacheExpiry.DISABLED, CacheExpiry.WRITE, CacheExpiry.ACCESS }, + expireAfterAccess = {Expire.DISABLED, Expire.ONE_MINUTE}, + expireAfterWrite = {Expire.DISABLED, Expire.ONE_MINUTE}, + expiryTime = Expire.ONE_MINUTE) public void estimatedSize(Cache cache, CacheContext context) { context.ticker().advance(1, TimeUnit.MINUTES); assertThat(cache.estimatedSize(), is(context.initialSize())); @@ -336,8 +376,11 @@ public void estimatedSize(Cache cache, CacheContext context) { @Test(dataProvider = "caches") @CacheSpec(population = { Population.SINGLETON, Population.PARTIAL, Population.FULL }, - requiresExpiration = true, expireAfterAccess = {Expire.DISABLED, Expire.ONE_MINUTE}, - expireAfterWrite = {Expire.DISABLED, Expire.ONE_MINUTE}) + mustExpiresWithAnyOf = { AFTER_ACCESS, AFTER_WRITE, VARIABLE }, + expiry = { CacheExpiry.DISABLED, CacheExpiry.WRITE, CacheExpiry.ACCESS }, + expireAfterAccess = {Expire.DISABLED, Expire.ONE_MINUTE}, + expireAfterWrite = {Expire.DISABLED, Expire.ONE_MINUTE}, + expiryTime = Expire.ONE_MINUTE) public void cleanUp(Cache cache, CacheContext context) { context.ticker().advance(1, TimeUnit.MINUTES); cache.cleanUp(); @@ -350,7 +393,9 @@ public void cleanUp(Cache cache, CacheContext context) { @Test(dataProvider = "caches") @CacheSpec(implementation = Implementation.Caffeine, keys = ReferenceType.STRONG, - population = Population.FULL, requiresExpiration = true, + population = Population.FULL, expiryTime = Expire.ONE_MINUTE, + mustExpiresWithAnyOf = { AFTER_ACCESS, AFTER_WRITE, VARIABLE }, + expiry = { CacheExpiry.DISABLED, 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) @@ -366,7 +411,9 @@ public void cleanUp_writerFails(Cache cache, CacheContext cont @Test(dataProvider = "caches", expectedExceptions = DeleteException.class) @CacheSpec(implementation = Implementation.Caffeine, keys = ReferenceType.STRONG, - population = Population.FULL, requiresExpiration = true, + population = Population.FULL, expiryTime = Expire.ONE_MINUTE, + mustExpiresWithAnyOf = { AFTER_ACCESS, AFTER_WRITE, VARIABLE }, + expiry = { CacheExpiry.DISABLED, 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) @@ -383,7 +430,9 @@ public void get_writerFails(LoadingCache cache, CacheContext c @Test(dataProvider = "caches", expectedExceptions = DeleteException.class) @CacheSpec(implementation = Implementation.Caffeine, keys = ReferenceType.STRONG, - population = Population.FULL, requiresExpiration = true, + population = Population.FULL, expiryTime = Expire.ONE_MINUTE, + mustExpiresWithAnyOf = { AFTER_ACCESS, AFTER_WRITE, VARIABLE }, + expiry = { CacheExpiry.DISABLED, 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) @@ -399,7 +448,9 @@ public void getAll_writerFails(LoadingCache cache, CacheContex } @Test(dataProvider = "caches") - @CacheSpec(population = Population.FULL, loader = Loader.IDENTITY, requiresExpiration = true, + @CacheSpec(population = Population.FULL, loader = Loader.IDENTITY, expiryTime = Expire.ONE_MINUTE, + mustExpiresWithAnyOf = { AFTER_ACCESS, AFTER_WRITE, VARIABLE }, + expiry = { CacheExpiry.DISABLED, CacheExpiry.WRITE, CacheExpiry.ACCESS }, expireAfterAccess = {Expire.DISABLED, Expire.ONE_MINUTE}, expireAfterWrite = {Expire.DISABLED, Expire.ONE_MINUTE}) public void refresh(LoadingCache cache, CacheContext context) { @@ -416,7 +467,9 @@ public void refresh(LoadingCache cache, CacheContext context) @Test(dataProvider = "caches") @CacheSpec(implementation = Implementation.Caffeine, keys = ReferenceType.STRONG, - population = Population.FULL, requiresExpiration = true, + population = Population.FULL, expiryTime = Expire.ONE_MINUTE, + mustExpiresWithAnyOf = { AFTER_ACCESS, AFTER_WRITE, VARIABLE }, + expiry = { CacheExpiry.DISABLED, 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 +485,9 @@ public void refresh_writerFails(LoadingCache cache, CacheConte @Test(dataProvider = "caches") @CacheSpec(population = Population.FULL, loader = Loader.IDENTITY, - removalListener = Listener.CONSUMING, requiresExpiration = true, + removalListener = Listener.CONSUMING, expiryTime = Expire.ONE_MINUTE, + mustExpiresWithAnyOf = { AFTER_ACCESS, AFTER_WRITE, VARIABLE }, + expiry = { CacheExpiry.DISABLED, CacheExpiry.WRITE, CacheExpiry.ACCESS }, expireAfterAccess = {Expire.DISABLED, Expire.ONE_MINUTE}, expireAfterWrite = {Expire.DISABLED, Expire.ONE_MINUTE}) @SuppressWarnings("FutureReturnValueIgnored") @@ -450,7 +505,9 @@ public void get(AsyncLoadingCache cache, CacheContext context) @Test(dataProvider = "caches") @CacheSpec(population = Population.EMPTY, removalListener = Listener.CONSUMING, - requiresExpiration = true, expireAfterAccess = {Expire.DISABLED, Expire.ONE_MINUTE}, + mustExpiresWithAnyOf = { AFTER_ACCESS, AFTER_WRITE, VARIABLE }, + expiry = { CacheExpiry.DISABLED, CacheExpiry.WRITE, CacheExpiry.ACCESS }, + expireAfterAccess = {Expire.DISABLED, Expire.ONE_MINUTE}, expiryTime = Expire.ONE_MINUTE, expireAfterWrite = {Expire.DISABLED, Expire.ONE_MINUTE}) @SuppressWarnings("FutureReturnValueIgnored") public void get_async(AsyncLoadingCache cache, CacheContext context) { @@ -473,11 +530,12 @@ public void get_async(AsyncLoadingCache cache, CacheContext co } @Test(dataProvider = "caches") - @CacheSpec(population = Population.FULL, - removalListener = Listener.CONSUMING, requiresExpiration = true, + @CacheSpec(population = Population.FULL, removalListener = Listener.CONSUMING, + mustExpiresWithAnyOf = { AFTER_ACCESS, AFTER_WRITE, VARIABLE }, + expiry = { CacheExpiry.DISABLED, CacheExpiry.WRITE, CacheExpiry.ACCESS }, expireAfterAccess = {Expire.DISABLED, Expire.ONE_MINUTE}, expireAfterWrite = {Expire.DISABLED, Expire.ONE_MINUTE}, - loader = {Loader.IDENTITY, Loader.BULK_IDENTITY}) + expiryTime = Expire.ONE_MINUTE, loader = {Loader.IDENTITY, Loader.BULK_IDENTITY}) @SuppressWarnings("FutureReturnValueIgnored") public void getAll(AsyncLoadingCache cache, CacheContext context) { Set keys = context.firstMiddleLastKeys(); @@ -490,20 +548,25 @@ public void getAll(AsyncLoadingCache cache, CacheContext conte } @Test(dataProvider = "caches") - @CacheSpec(population = Population.FULL, requiresExpiration = true, - expireAfterAccess = {Expire.DISABLED, Expire.ONE_MINUTE}, + @CacheSpec(population = Population.FULL, + mustExpiresWithAnyOf = { AFTER_ACCESS, AFTER_WRITE, VARIABLE }, + expiry = { CacheExpiry.DISABLED, 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) { context.ticker().advance(1, TimeUnit.MINUTES); cache.put(context.firstKey(), CompletableFuture.completedFuture(context.absentValue())); + runVariableExpiration(context); long count = context.initialSize(); assertThat(cache.synchronous().estimatedSize(), is(1L)); assertThat(cache, hasRemovalNotifications(context, count, RemovalCause.EXPIRED)); } @Test(dataProvider = "caches") - @CacheSpec(population = Population.FULL, requiresExpiration = true, + @CacheSpec(population = Population.FULL, expiryTime = Expire.ONE_MINUTE, + mustExpiresWithAnyOf = { AFTER_ACCESS, AFTER_WRITE, VARIABLE }, + expiry = { CacheExpiry.DISABLED, CacheExpiry.WRITE, CacheExpiry.ACCESS }, expireAfterAccess = {Expire.DISABLED, Expire.ONE_MINUTE}, expireAfterWrite = {Expire.DISABLED, Expire.ONE_MINUTE}) public void put_replace(AsyncLoadingCache cache, CacheContext context) { @@ -528,7 +591,9 @@ public void put_replace(AsyncLoadingCache cache, CacheContext /* ---------------- Map -------------- */ @Test(dataProvider = "caches") - @CacheSpec(population = Population.FULL, requiresExpiration = true, + @CacheSpec(population = Population.FULL, expiryTime = Expire.ONE_MINUTE, + mustExpiresWithAnyOf = { AFTER_ACCESS, AFTER_WRITE, VARIABLE }, + expiry = { CacheExpiry.DISABLED, CacheExpiry.WRITE, CacheExpiry.ACCESS }, expireAfterAccess = {Expire.DISABLED, Expire.ONE_MINUTE}, expireAfterWrite = {Expire.DISABLED, Expire.ONE_MINUTE}) public void isEmpty(Map map, CacheContext context) { @@ -537,7 +602,9 @@ public void isEmpty(Map map, CacheContext context) { } @Test(dataProvider = "caches") - @CacheSpec(population = Population.FULL, requiresExpiration = true, + @CacheSpec(population = Population.FULL, expiryTime = Expire.ONE_MINUTE, + mustExpiresWithAnyOf = { AFTER_ACCESS, AFTER_WRITE, VARIABLE }, + expiry = { CacheExpiry.DISABLED, CacheExpiry.WRITE, CacheExpiry.ACCESS }, expireAfterAccess = {Expire.DISABLED, Expire.ONE_MINUTE}, expireAfterWrite = {Expire.DISABLED, Expire.ONE_MINUTE}) public void size(Map map, CacheContext context) { @@ -546,7 +613,9 @@ public void size(Map map, CacheContext context) { } @Test(dataProvider = "caches") - @CacheSpec(population = Population.FULL, requiresExpiration = true, + @CacheSpec(population = Population.FULL, expiryTime = Expire.ONE_MINUTE, + mustExpiresWithAnyOf = { AFTER_ACCESS, AFTER_WRITE, VARIABLE }, + expiry = { CacheExpiry.DISABLED, CacheExpiry.WRITE, CacheExpiry.ACCESS }, expireAfterAccess = {Expire.DISABLED, Expire.ONE_MINUTE}, expireAfterWrite = {Expire.DISABLED, Expire.ONE_MINUTE}) public void containsKey(Map map, CacheContext context) { @@ -555,7 +624,9 @@ public void containsKey(Map map, CacheContext context) { } @Test(dataProvider = "caches") - @CacheSpec(population = Population.FULL, requiresExpiration = true, + @CacheSpec(population = Population.FULL, expiryTime = Expire.ONE_MINUTE, + mustExpiresWithAnyOf = { AFTER_ACCESS, AFTER_WRITE, VARIABLE }, + expiry = { CacheExpiry.DISABLED, CacheExpiry.WRITE, CacheExpiry.ACCESS }, expireAfterAccess = {Expire.DISABLED, Expire.ONE_MINUTE}, expireAfterWrite = {Expire.DISABLED, Expire.ONE_MINUTE}) public void containsValue(Map map, CacheContext context) { @@ -564,7 +635,9 @@ public void containsValue(Map map, CacheContext context) { } @Test(dataProvider = "caches") - @CacheSpec(population = Population.FULL, requiresExpiration = true, + @CacheSpec(population = Population.FULL, expiryTime = Expire.ONE_MINUTE, + mustExpiresWithAnyOf = { AFTER_ACCESS, AFTER_WRITE, VARIABLE }, + expiry = { CacheExpiry.DISABLED, CacheExpiry.WRITE, CacheExpiry.ACCESS }, expireAfterAccess = {Expire.DISABLED, Expire.ONE_MINUTE}, expireAfterWrite = {Expire.DISABLED, Expire.ONE_MINUTE}) public void clear(Map map, CacheContext context) { @@ -578,7 +651,9 @@ public void clear(Map map, CacheContext context) { @Test(dataProvider = "caches", expectedExceptions = DeleteException.class) @CacheSpec(implementation = Implementation.Caffeine, keys = ReferenceType.STRONG, - population = Population.FULL, requiresExpiration = true, + population = Population.FULL, expiryTime = Expire.ONE_MINUTE, + mustExpiresWithAnyOf = { AFTER_ACCESS, AFTER_WRITE, VARIABLE }, + expiry = { CacheExpiry.DISABLED, 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) @@ -595,7 +670,9 @@ public void clear_writerFails(Map map, CacheContext context) { @Test(dataProvider = "caches", expectedExceptions = DeleteException.class) @CacheSpec(implementation = Implementation.Caffeine, keys = ReferenceType.STRONG, - population = Population.FULL, requiresExpiration = true, + population = Population.FULL, expiryTime = Expire.ONE_MINUTE, + mustExpiresWithAnyOf = { AFTER_ACCESS, AFTER_WRITE, VARIABLE }, + expiry = { CacheExpiry.DISABLED, 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) @@ -611,7 +688,9 @@ public void putIfAbsent_writerFails(Map map, CacheContext cont } @Test(dataProvider = "caches") - @CacheSpec(population = Population.FULL, requiresExpiration = true, + @CacheSpec(population = Population.FULL, expiryTime = Expire.ONE_MINUTE, + mustExpiresWithAnyOf = { AFTER_ACCESS, AFTER_WRITE, VARIABLE }, + expiry = { CacheExpiry.DISABLED, CacheExpiry.WRITE, CacheExpiry.ACCESS }, expireAfterAccess = {Expire.DISABLED, Expire.ONE_MINUTE}, expireAfterWrite = {Expire.DISABLED, Expire.ONE_MINUTE}) public void put_insert(Map map, CacheContext context) { @@ -625,7 +704,9 @@ public void put_insert(Map map, CacheContext context) { } @Test(dataProvider = "caches") - @CacheSpec(population = Population.FULL, requiresExpiration = true, + @CacheSpec(population = Population.FULL, expiryTime = Expire.ONE_MINUTE, + mustExpiresWithAnyOf = { AFTER_ACCESS, AFTER_WRITE, VARIABLE }, + expiry = { CacheExpiry.DISABLED, CacheExpiry.WRITE, CacheExpiry.ACCESS }, expireAfterAccess = {Expire.DISABLED, Expire.ONE_MINUTE}, expireAfterWrite = {Expire.DISABLED, Expire.ONE_MINUTE}) public void put_replace(Map map, CacheContext context) { @@ -652,7 +733,9 @@ public void put_replace(Map map, CacheContext context) { @Test(dataProvider = "caches", expectedExceptions = DeleteException.class) @CacheSpec(implementation = Implementation.Caffeine, keys = ReferenceType.STRONG, - population = Population.FULL, requiresExpiration = true, + population = Population.FULL, expiryTime = Expire.ONE_MINUTE, + mustExpiresWithAnyOf = { AFTER_ACCESS, AFTER_WRITE, VARIABLE }, + expiry = { CacheExpiry.DISABLED, 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) @@ -668,7 +751,9 @@ public void put_writerFails(Map map, CacheContext context) { } @Test(dataProvider = "caches") - @CacheSpec(population = Population.FULL, requiresExpiration = true, + @CacheSpec(population = Population.FULL, expiryTime = Expire.ONE_MINUTE, + mustExpiresWithAnyOf = { AFTER_ACCESS, AFTER_WRITE, VARIABLE }, + expiry = { CacheExpiry.DISABLED, CacheExpiry.WRITE, CacheExpiry.ACCESS }, expireAfterAccess = {Expire.DISABLED, Expire.ONE_MINUTE}, expireAfterWrite = {Expire.DISABLED, Expire.ONE_MINUTE}) public void replace(Map map, CacheContext context) { @@ -685,7 +770,9 @@ public void replace(Map map, CacheContext context) { } @Test(dataProvider = "caches") - @CacheSpec(population = Population.FULL, requiresExpiration = true, + @CacheSpec(population = Population.FULL, expiryTime = Expire.ONE_MINUTE, + mustExpiresWithAnyOf = { AFTER_ACCESS, AFTER_WRITE, VARIABLE }, + expiry = { CacheExpiry.DISABLED, CacheExpiry.WRITE, CacheExpiry.ACCESS }, expireAfterAccess = {Expire.DISABLED, Expire.ONE_MINUTE}, expireAfterWrite = {Expire.DISABLED, Expire.ONE_MINUTE}) public void replace_updated(Map map, CacheContext context) { @@ -702,7 +789,9 @@ public void replace_updated(Map map, CacheContext context) { // replace_writerFail: Not needed due to exiting without side-effects @Test(dataProvider = "caches") - @CacheSpec(population = Population.FULL, requiresExpiration = true, + @CacheSpec(population = Population.FULL, expiryTime = Expire.ONE_MINUTE, + mustExpiresWithAnyOf = { AFTER_ACCESS, AFTER_WRITE, VARIABLE }, + expiry = { CacheExpiry.DISABLED, CacheExpiry.WRITE, CacheExpiry.ACCESS }, expireAfterAccess = {Expire.DISABLED, Expire.ONE_MINUTE}, expireAfterWrite = {Expire.DISABLED, Expire.ONE_MINUTE}) public void replaceConditionally(Map map, CacheContext context) { @@ -720,7 +809,9 @@ public void replaceConditionally(Map map, CacheContext context } @Test(dataProvider = "caches") - @CacheSpec(population = Population.FULL, requiresExpiration = true, + @CacheSpec(population = Population.FULL, expiryTime = Expire.ONE_MINUTE, + mustExpiresWithAnyOf = { AFTER_ACCESS, AFTER_WRITE, VARIABLE }, + expiry = { CacheExpiry.DISABLED, CacheExpiry.WRITE, CacheExpiry.ACCESS }, expireAfterAccess = {Expire.DISABLED, Expire.ONE_MINUTE}, expireAfterWrite = {Expire.DISABLED, Expire.ONE_MINUTE}) public void replaceConditionally_updated(Map map, CacheContext context) { @@ -738,7 +829,9 @@ public void replaceConditionally_updated(Map map, CacheContext // replaceConditionally_writerFail: Not needed due to exiting without side-effects @Test(dataProvider = "caches") - @CacheSpec(population = Population.FULL, requiresExpiration = true, + @CacheSpec(population = Population.FULL, expiryTime = Expire.ONE_MINUTE, + mustExpiresWithAnyOf = { AFTER_ACCESS, AFTER_WRITE, VARIABLE }, + expiry = { CacheExpiry.DISABLED, CacheExpiry.WRITE, CacheExpiry.ACCESS }, expireAfterAccess = {Expire.DISABLED, Expire.ONE_MINUTE}, expireAfterWrite = {Expire.DISABLED, Expire.ONE_MINUTE}) public void remove(Map map, CacheContext context) { @@ -752,7 +845,9 @@ public void remove(Map map, CacheContext context) { @Test(dataProvider = "caches", expectedExceptions = DeleteException.class) @CacheSpec(implementation = Implementation.Caffeine, keys = ReferenceType.STRONG, - population = Population.FULL, compute = Compute.SYNC, requiresExpiration = true, + population = Population.FULL, compute = Compute.SYNC, expiryTime = Expire.ONE_MINUTE, + mustExpiresWithAnyOf = { AFTER_ACCESS, AFTER_WRITE, VARIABLE }, + expiry = { CacheExpiry.DISABLED, CacheExpiry.WRITE, CacheExpiry.ACCESS }, expireAfterAccess = {Expire.DISABLED, Expire.ONE_MINUTE}, expireAfterWrite = {Expire.DISABLED, Expire.ONE_MINUTE}, writer = Writer.EXCEPTIONAL, removalListener = Listener.REJECTING) @@ -768,7 +863,9 @@ public void remove_writerFails(Map map, CacheContext context) } @Test(dataProvider = "caches") - @CacheSpec(population = Population.FULL, requiresExpiration = true, + @CacheSpec(population = Population.FULL, expiryTime = Expire.ONE_MINUTE, + mustExpiresWithAnyOf = { AFTER_ACCESS, AFTER_WRITE, VARIABLE }, + expiry = { CacheExpiry.DISABLED, CacheExpiry.WRITE, CacheExpiry.ACCESS }, expireAfterAccess = {Expire.DISABLED, Expire.ONE_MINUTE}, expireAfterWrite = {Expire.DISABLED, Expire.ONE_MINUTE}) public void removeConditionally(Map map, CacheContext context) { @@ -783,7 +880,9 @@ public void removeConditionally(Map map, CacheContext context) @Test(dataProvider = "caches", expectedExceptions = DeleteException.class) @CacheSpec(implementation = Implementation.Caffeine, keys = ReferenceType.STRONG, - population = Population.FULL, requiresExpiration = true, + population = Population.FULL, expiryTime = Expire.ONE_MINUTE, + mustExpiresWithAnyOf = { AFTER_ACCESS, AFTER_WRITE, VARIABLE }, + expiry = { CacheExpiry.DISABLED, 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) @@ -800,7 +899,9 @@ public void removeConditionally_writerFails(Map map, CacheCont } @Test(dataProvider = "caches") - @CacheSpec(population = Population.FULL, requiresExpiration = true, + @CacheSpec(population = Population.FULL, expiryTime = Expire.ONE_MINUTE, + mustExpiresWithAnyOf = { AFTER_ACCESS, AFTER_WRITE, VARIABLE }, + expiry = { CacheExpiry.DISABLED, CacheExpiry.WRITE, CacheExpiry.ACCESS }, expireAfterAccess = {Expire.DISABLED, Expire.ONE_MINUTE}, expireAfterWrite = {Expire.DISABLED, Expire.ONE_MINUTE}) public void computeIfAbsent(Map map, CacheContext context) { @@ -816,7 +917,9 @@ public void computeIfAbsent(Map map, CacheContext context) { @Test(dataProvider = "caches", expectedExceptions = DeleteException.class) @CacheSpec(implementation = Implementation.Caffeine, keys = ReferenceType.STRONG, - population = Population.FULL, requiresExpiration = true, + population = Population.FULL, expiryTime = Expire.ONE_MINUTE, + mustExpiresWithAnyOf = { AFTER_ACCESS, AFTER_WRITE, VARIABLE }, + expiry = { CacheExpiry.DISABLED, 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) @@ -833,7 +936,9 @@ public void computeIfAbsent_writerFails(Map map, CacheContext } @Test(dataProvider = "caches") - @CacheSpec(population = Population.FULL, requiresExpiration = true, + @CacheSpec(population = Population.FULL, expiryTime = Expire.ONE_MINUTE, + mustExpiresWithAnyOf = { AFTER_ACCESS, AFTER_WRITE, VARIABLE }, + expiry = { CacheExpiry.DISABLED, CacheExpiry.WRITE, CacheExpiry.ACCESS }, expireAfterAccess = {Expire.DISABLED, Expire.ONE_MINUTE}, expireAfterWrite = {Expire.DISABLED, Expire.ONE_MINUTE}) public void computeIfPresent(Map map, CacheContext context) { @@ -854,7 +959,9 @@ public void computeIfPresent(Map map, CacheContext context) { @Test(dataProvider = "caches", expectedExceptions = DeleteException.class) @CacheSpec(implementation = Implementation.Caffeine, keys = ReferenceType.STRONG, - population = Population.FULL, requiresExpiration = true, + population = Population.FULL, expiryTime = Expire.ONE_MINUTE, + mustExpiresWithAnyOf = { AFTER_ACCESS, AFTER_WRITE, VARIABLE }, + expiry = { CacheExpiry.DISABLED, CacheExpiry.WRITE, CacheExpiry.ACCESS }, expireAfterAccess = {Expire.DISABLED, Expire.ONE_MINUTE}, expireAfterWrite = {Expire.DISABLED, Expire.ONE_MINUTE}, compute = Compute.SYNC, writer = Writer.EXCEPTIONAL, @@ -872,7 +979,9 @@ public void computeIfPresent_writerFails(Map map, CacheContext } @Test(dataProvider = "caches") - @CacheSpec(population = Population.FULL, requiresExpiration = true, + @CacheSpec(population = Population.FULL, expiryTime = Expire.ONE_MINUTE, + mustExpiresWithAnyOf = { AFTER_ACCESS, AFTER_WRITE, VARIABLE }, + expiry = { CacheExpiry.DISABLED, CacheExpiry.WRITE, CacheExpiry.ACCESS }, expireAfterAccess = {Expire.DISABLED, Expire.ONE_MINUTE}, expireAfterWrite = {Expire.DISABLED, Expire.ONE_MINUTE}) public void compute(Map map, CacheContext context) { @@ -891,7 +1000,9 @@ public void compute(Map map, CacheContext context) { @Test(dataProvider = "caches", expectedExceptions = DeleteException.class) @CacheSpec(implementation = Implementation.Caffeine, keys = ReferenceType.STRONG, - population = Population.FULL, requiresExpiration = true, + population = Population.FULL, expiryTime = Expire.ONE_MINUTE, + mustExpiresWithAnyOf = { AFTER_ACCESS, AFTER_WRITE, VARIABLE }, + expiry = { CacheExpiry.DISABLED, 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) @@ -908,7 +1019,9 @@ public void compute_writerFails(Map map, CacheContext context) } @Test(dataProvider = "caches") - @CacheSpec(population = Population.FULL, requiresExpiration = true, + @CacheSpec(population = Population.FULL, expiryTime = Expire.ONE_MINUTE, + mustExpiresWithAnyOf = { AFTER_ACCESS, AFTER_WRITE, VARIABLE }, + expiry = { CacheExpiry.DISABLED, CacheExpiry.WRITE, CacheExpiry.ACCESS }, expireAfterAccess = {Expire.DISABLED, Expire.ONE_MINUTE}, expireAfterWrite = {Expire.DISABLED, Expire.ONE_MINUTE}) public void merge(Map map, CacheContext context) { @@ -926,7 +1039,9 @@ public void merge(Map map, CacheContext context) { @Test(dataProvider = "caches", expectedExceptions = DeleteException.class) @CacheSpec(implementation = Implementation.Caffeine, keys = ReferenceType.STRONG, - population = Population.FULL, requiresExpiration = true, + population = Population.FULL, expiryTime = Expire.ONE_MINUTE, + mustExpiresWithAnyOf = { AFTER_ACCESS, AFTER_WRITE, VARIABLE }, + expiry = { CacheExpiry.DISABLED, 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) @@ -944,7 +1059,9 @@ public void merge_writerFails(Map map, CacheContext context) { } @Test(dataProvider = "caches") - @CacheSpec(population = Population.FULL, requiresExpiration = true, + @CacheSpec(population = Population.FULL, expiryTime = Expire.ONE_MINUTE, + mustExpiresWithAnyOf = { AFTER_ACCESS, AFTER_WRITE, VARIABLE }, + expiry = { CacheExpiry.DISABLED, CacheExpiry.WRITE, CacheExpiry.ACCESS }, expireAfterAccess = {Expire.DISABLED, Expire.ONE_MINUTE}, expireAfterWrite = {Expire.DISABLED, Expire.ONE_MINUTE}) public void iterators(Map map, CacheContext context) { @@ -958,8 +1075,10 @@ public void iterators(Map map, CacheContext context) { @Test(dataProvider = "caches") @CacheSpec(implementation = Implementation.Caffeine, population = Population.EMPTY, - maximumSize = Maximum.FULL, weigher = CacheWeigher.COLLECTION, - requiresExpiration = true, expireAfterAccess = {Expire.DISABLED, Expire.ONE_MINUTE}, + maximumSize = Maximum.FULL, weigher = CacheWeigher.COLLECTION, expiryTime = Expire.ONE_MINUTE, + mustExpiresWithAnyOf = { AFTER_ACCESS, AFTER_WRITE, VARIABLE }, + expiry = { CacheExpiry.DISABLED, CacheExpiry.WRITE, CacheExpiry.ACCESS }, + expireAfterAccess = {Expire.DISABLED, Expire.ONE_MINUTE}, expireAfterWrite = {Expire.DISABLED, Expire.ONE_MINUTE}) public void putIfAbsent_weighted(Cache> cache, CacheContext context) { cache.put(1, ImmutableList.of(1)); @@ -971,8 +1090,10 @@ public void putIfAbsent_weighted(Cache> cache, CacheConte @Test(dataProvider = "caches") @CacheSpec(implementation = Implementation.Caffeine, population = Population.EMPTY, - maximumSize = Maximum.FULL, weigher = CacheWeigher.COLLECTION, - requiresExpiration = true, expireAfterAccess = {Expire.DISABLED, Expire.ONE_MINUTE}, + maximumSize = Maximum.FULL, weigher = CacheWeigher.COLLECTION, expiryTime = Expire.ONE_MINUTE, + mustExpiresWithAnyOf = { AFTER_ACCESS, AFTER_WRITE, VARIABLE }, + expiry = { CacheExpiry.DISABLED, CacheExpiry.WRITE, CacheExpiry.ACCESS }, + expireAfterAccess = {Expire.DISABLED, Expire.ONE_MINUTE}, expireAfterWrite = {Expire.DISABLED, Expire.ONE_MINUTE}) public void put_weighted(Cache> cache, CacheContext context) { cache.put(1, ImmutableList.of(1)); @@ -984,8 +1105,10 @@ public void put_weighted(Cache> cache, CacheContext conte @Test(dataProvider = "caches") @CacheSpec(implementation = Implementation.Caffeine, population = Population.EMPTY, - maximumSize = Maximum.FULL, weigher = CacheWeigher.COLLECTION, - requiresExpiration = true, expireAfterAccess = {Expire.DISABLED, Expire.ONE_MINUTE}, + maximumSize = Maximum.FULL, weigher = CacheWeigher.COLLECTION, expiryTime = Expire.ONE_MINUTE, + mustExpiresWithAnyOf = { AFTER_ACCESS, AFTER_WRITE, VARIABLE }, + expiry = { CacheExpiry.DISABLED, CacheExpiry.WRITE, CacheExpiry.ACCESS }, + expireAfterAccess = {Expire.DISABLED, Expire.ONE_MINUTE}, expireAfterWrite = {Expire.DISABLED, Expire.ONE_MINUTE}) public void computeIfAbsent_weighted(Cache> cache, CacheContext context) { cache.put(1, ImmutableList.of(1)); @@ -997,8 +1120,10 @@ public void computeIfAbsent_weighted(Cache> cache, CacheC @Test(dataProvider = "caches") @CacheSpec(implementation = Implementation.Caffeine, population = Population.EMPTY, - maximumSize = Maximum.FULL, weigher = CacheWeigher.COLLECTION, - requiresExpiration = true, expireAfterAccess = {Expire.DISABLED, Expire.ONE_MINUTE}, + maximumSize = Maximum.FULL, weigher = CacheWeigher.COLLECTION, expiryTime = Expire.ONE_MINUTE, + mustExpiresWithAnyOf = { AFTER_ACCESS, AFTER_WRITE, VARIABLE }, + expiry = { CacheExpiry.DISABLED, CacheExpiry.WRITE, CacheExpiry.ACCESS }, + expireAfterAccess = {Expire.DISABLED, Expire.ONE_MINUTE}, expireAfterWrite = {Expire.DISABLED, Expire.ONE_MINUTE}) public void compute_weighted(Cache> cache, CacheContext context) { cache.put(1, ImmutableList.of(1)); @@ -1010,8 +1135,10 @@ public void compute_weighted(Cache> cache, CacheContext c @Test(dataProvider = "caches") @CacheSpec(implementation = Implementation.Caffeine, population = Population.EMPTY, - maximumSize = Maximum.FULL, weigher = CacheWeigher.COLLECTION, - requiresExpiration = true, expireAfterAccess = {Expire.DISABLED, Expire.ONE_MINUTE}, + maximumSize = Maximum.FULL, weigher = CacheWeigher.COLLECTION, expiryTime = Expire.ONE_MINUTE, + mustExpiresWithAnyOf = { AFTER_ACCESS, AFTER_WRITE, VARIABLE }, + expiry = { CacheExpiry.DISABLED, CacheExpiry.WRITE, CacheExpiry.ACCESS }, + expireAfterAccess = {Expire.DISABLED, Expire.ONE_MINUTE}, expireAfterWrite = {Expire.DISABLED, Expire.ONE_MINUTE}) public void merge_weighted(Cache> cache, CacheContext context) { cache.put(1, ImmutableList.of(1)); @@ -1022,4 +1149,15 @@ public void merge_weighted(Cache> cache, CacheContext con assertThat(cache.policy().eviction().get().weightedSize().getAsLong(), is(3L)); } + + /** + * Ensures that variable expiration is run, as it may not have due to expiring in coarse batches. + */ + private static void runVariableExpiration(CacheContext context) { + if (context.expiresVariably()) { + // Variable expires in coarse buckets at a time + context.ticker().advance(2, TimeUnit.SECONDS); + context.cleanUp(); + } + } } diff --git a/caffeine/src/test/java/com/github/benmanes/caffeine/cache/ExpireAfterAccessTest.java b/caffeine/src/test/java/com/github/benmanes/caffeine/cache/ExpireAfterAccessTest.java index 63193ef723..967b713c66 100644 --- a/caffeine/src/test/java/com/github/benmanes/caffeine/cache/ExpireAfterAccessTest.java +++ b/caffeine/src/test/java/com/github/benmanes/caffeine/cache/ExpireAfterAccessTest.java @@ -15,6 +15,8 @@ */ package com.github.benmanes.caffeine.cache; +import static com.github.benmanes.caffeine.cache.testing.CacheSpec.Expiration.AFTER_ACCESS; +import static com.github.benmanes.caffeine.cache.testing.CacheSpec.Expiration.VARIABLE; 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; @@ -40,6 +42,7 @@ import com.github.benmanes.caffeine.cache.testing.CacheContext; import com.github.benmanes.caffeine.cache.testing.CacheProvider; import com.github.benmanes.caffeine.cache.testing.CacheSpec; +import com.github.benmanes.caffeine.cache.testing.CacheSpec.CacheExpiry; import com.github.benmanes.caffeine.cache.testing.CacheSpec.Expire; import com.github.benmanes.caffeine.cache.testing.CacheSpec.Implementation; import com.github.benmanes.caffeine.cache.testing.CacheSpec.Listener; @@ -62,8 +65,9 @@ public final class ExpireAfterAccessTest { /* ---------------- Cache -------------- */ @Test(dataProvider = "caches") - @CacheSpec(expireAfterAccess = Expire.ONE_MINUTE, - population = { Population.PARTIAL, Population.FULL }) + @CacheSpec(mustExpiresWithAnyOf = { AFTER_ACCESS, VARIABLE }, + expiry = { CacheExpiry.DISABLED, CacheExpiry.ACCESS }, expiryTime = Expire.ONE_MINUTE, + expireAfterAccess = Expire.ONE_MINUTE, population = { Population.PARTIAL, Population.FULL }) public void getIfPresent(Cache cache, CacheContext context) { context.ticker().advance(30, TimeUnit.SECONDS); cache.getIfPresent(context.firstKey()); @@ -80,8 +84,9 @@ public void getIfPresent(Cache cache, CacheContext context) { } @Test(dataProvider = "caches") - @CacheSpec(expireAfterAccess = Expire.ONE_MINUTE, - population = { Population.PARTIAL, Population.FULL }) + @CacheSpec(mustExpiresWithAnyOf = { AFTER_ACCESS, VARIABLE }, + expiry = { CacheExpiry.DISABLED, CacheExpiry.ACCESS }, expiryTime = Expire.ONE_MINUTE, + expireAfterAccess = Expire.ONE_MINUTE, population = { Population.PARTIAL, Population.FULL }) public void get(Cache cache, CacheContext context) { Function mappingFunction = context.original()::get; context.ticker().advance(30, TimeUnit.SECONDS); @@ -99,8 +104,9 @@ public void get(Cache cache, CacheContext context) { } @Test(dataProvider = "caches") - @CacheSpec(expireAfterAccess = Expire.ONE_MINUTE, - population = { Population.PARTIAL, Population.FULL }) + @CacheSpec(mustExpiresWithAnyOf = { AFTER_ACCESS, VARIABLE }, + expiry = { CacheExpiry.DISABLED, CacheExpiry.ACCESS }, expiryTime = Expire.ONE_MINUTE, + expireAfterAccess = Expire.ONE_MINUTE, population = { Population.PARTIAL, Population.FULL }) public void getAllPresent(Cache cache, CacheContext context) { context.ticker().advance(30, TimeUnit.SECONDS); cache.getAllPresent(context.firstMiddleLastKeys()); @@ -118,8 +124,9 @@ public void getAllPresent(Cache cache, CacheContext context) { /* ---------------- LoadingCache -------------- */ @Test(dataProvider = "caches") - @CacheSpec(expireAfterAccess = Expire.ONE_MINUTE, loader = Loader.IDENTITY, - population = { Population.PARTIAL, Population.FULL }) + @CacheSpec(mustExpiresWithAnyOf = { AFTER_ACCESS, VARIABLE }, expiryTime = Expire.ONE_MINUTE, + expiry = { CacheExpiry.DISABLED, CacheExpiry.ACCESS }, expireAfterAccess = Expire.ONE_MINUTE, + loader = Loader.IDENTITY, population = { Population.PARTIAL, Population.FULL }) public void get(LoadingCache cache, CacheContext context) { context.ticker().advance(30, TimeUnit.SECONDS); cache.get(context.firstKey()); @@ -138,8 +145,9 @@ public void get(LoadingCache cache, CacheContext context) { } @Test(dataProvider = "caches") - @CacheSpec(expireAfterAccess = Expire.ONE_MINUTE, - loader = {Loader.IDENTITY, Loader.BULK_IDENTITY}, + @CacheSpec(mustExpiresWithAnyOf = { AFTER_ACCESS, VARIABLE }, + expiry = { CacheExpiry.DISABLED, CacheExpiry.ACCESS }, expiryTime = Expire.ONE_MINUTE, + expireAfterAccess = Expire.ONE_MINUTE, loader = {Loader.IDENTITY, Loader.BULK_IDENTITY}, population = { Population.PARTIAL, Population.FULL }) public void getAll(LoadingCache cache, CacheContext context) { context.ticker().advance(30, TimeUnit.SECONDS); @@ -168,8 +176,9 @@ public void getAll(LoadingCache cache, CacheContext context) { /* ---------------- AsyncLoadingCache -------------- */ @Test(dataProvider = "caches") - @CacheSpec(expireAfterAccess = Expire.ONE_MINUTE, - population = { Population.PARTIAL, Population.FULL }) + @CacheSpec(mustExpiresWithAnyOf = { AFTER_ACCESS, VARIABLE }, + expiry = { CacheExpiry.DISABLED, CacheExpiry.ACCESS }, expiryTime = Expire.ONE_MINUTE, + expireAfterAccess = Expire.ONE_MINUTE, population = { Population.PARTIAL, Population.FULL }) @SuppressWarnings("FutureReturnValueIgnored") public void getIfPresent(AsyncLoadingCache cache, CacheContext context) { context.ticker().advance(30, TimeUnit.SECONDS); @@ -186,7 +195,9 @@ public void getIfPresent(AsyncLoadingCache cache, CacheContext /* ---------------- Map -------------- */ @Test(dataProvider = "caches") - @CacheSpec(expireAfterAccess = Expire.ONE_MINUTE, population = Population.FULL) + @CacheSpec(mustExpiresWithAnyOf = { AFTER_ACCESS, VARIABLE }, + expiry = { CacheExpiry.DISABLED, CacheExpiry.ACCESS }, expiryTime = Expire.ONE_MINUTE, + expireAfterAccess = Expire.ONE_MINUTE, population = Population.FULL) public void putIfAbsent(Map map, CacheContext context) { context.ticker().advance(30, TimeUnit.SECONDS); assertThat(map.putIfAbsent(context.firstKey(), context.absentValue()), is(not(nullValue()))); @@ -203,14 +214,18 @@ public void putIfAbsent(Map map, CacheContext context) { /* ---------------- Policy -------------- */ @Test(dataProvider = "caches") - @CacheSpec(implementation = Implementation.Caffeine, expireAfterAccess = Expire.ONE_MINUTE) + @CacheSpec(implementation = Implementation.Caffeine, expireAfterAccess = Expire.ONE_MINUTE, + mustExpiresWithAnyOf = { AFTER_ACCESS, VARIABLE }, expiryTime = Expire.ONE_MINUTE, + expiry = { CacheExpiry.DISABLED, CacheExpiry.ACCESS }) public void getExpiresAfter(CacheContext context, @ExpireAfterAccess Expiration expireAfterAccess) { assertThat(expireAfterAccess.getExpiresAfter(TimeUnit.MINUTES), is(1L)); } @Test(dataProvider = "caches") - @CacheSpec(implementation = Implementation.Caffeine, expireAfterAccess = Expire.ONE_MINUTE) + @CacheSpec(implementation = Implementation.Caffeine, expireAfterAccess = Expire.ONE_MINUTE, + mustExpiresWithAnyOf = { AFTER_ACCESS, VARIABLE }, expiryTime = Expire.ONE_MINUTE, + expiry = { CacheExpiry.DISABLED, CacheExpiry.ACCESS }) public void setExpiresAfter(Cache cache, CacheContext context, @ExpireAfterAccess Expiration expireAfterAccess) { expireAfterAccess.setExpiresAfter(2, TimeUnit.MINUTES); @@ -223,7 +238,9 @@ public void setExpiresAfter(Cache cache, CacheContext context, @Test(dataProvider = "caches") @CacheSpec(implementation = Implementation.Caffeine, expireAfterAccess = Expire.ONE_MINUTE, - population = { Population.SINGLETON, Population.PARTIAL, Population.FULL }) + population = { Population.SINGLETON, Population.PARTIAL, Population.FULL }, + mustExpiresWithAnyOf = { AFTER_ACCESS, VARIABLE }, expiryTime = Expire.ONE_MINUTE, + expiry = { CacheExpiry.DISABLED, CacheExpiry.ACCESS }) public void ageOf(CacheContext context, @ExpireAfterAccess Expiration expireAfterAccess) { assertThat(expireAfterAccess.ageOf(context.firstKey(), TimeUnit.SECONDS).getAsLong(), is(0L)); @@ -235,14 +252,18 @@ public void ageOf(CacheContext context, /* ---------------- Policy: oldest -------------- */ - @CacheSpec(implementation = Implementation.Caffeine, expireAfterAccess = Expire.ONE_MINUTE) + @CacheSpec(implementation = Implementation.Caffeine, expireAfterAccess = Expire.ONE_MINUTE, + mustExpiresWithAnyOf = { AFTER_ACCESS, VARIABLE }, expiryTime = Expire.ONE_MINUTE, + expiry = { CacheExpiry.DISABLED, CacheExpiry.ACCESS }) @Test(dataProvider = "caches", expectedExceptions = UnsupportedOperationException.class) public void oldest_unmodifiable(CacheContext context, @ExpireAfterAccess Expiration expireAfterAccess) { expireAfterAccess.oldest(Integer.MAX_VALUE).clear();; } - @CacheSpec(implementation = Implementation.Caffeine, expireAfterAccess = Expire.ONE_MINUTE) + @CacheSpec(implementation = Implementation.Caffeine, expireAfterAccess = Expire.ONE_MINUTE, + mustExpiresWithAnyOf = { AFTER_ACCESS, VARIABLE }, expiryTime = Expire.ONE_MINUTE, + expiry = { CacheExpiry.DISABLED, CacheExpiry.ACCESS }) @Test(dataProvider = "caches", expectedExceptions = IllegalArgumentException.class) public void oldest_negative(CacheContext context, @ExpireAfterAccess Expiration expireAfterAccess) { @@ -250,15 +271,18 @@ public void oldest_negative(CacheContext context, } @Test(dataProvider = "caches") - @CacheSpec(implementation = Implementation.Caffeine, expireAfterAccess = Expire.ONE_MINUTE) + @CacheSpec(implementation = Implementation.Caffeine, expireAfterAccess = Expire.ONE_MINUTE, + mustExpiresWithAnyOf = { AFTER_ACCESS, VARIABLE }, expiryTime = Expire.ONE_MINUTE, + expiry = { CacheExpiry.DISABLED, CacheExpiry.ACCESS }) public void oldest_zero(CacheContext context, @ExpireAfterAccess Expiration expireAfterAccess) { assertThat(expireAfterAccess.oldest(0), is(emptyMap())); } @Test(dataProvider = "caches") - @CacheSpec(implementation = Implementation.Caffeine, - population = Population.FULL, expireAfterAccess = Expire.ONE_MINUTE) + @CacheSpec(implementation = Implementation.Caffeine, population = Population.FULL, + mustExpiresWithAnyOf = { AFTER_ACCESS, VARIABLE }, expireAfterAccess = Expire.ONE_MINUTE, + expiry = { CacheExpiry.DISABLED, CacheExpiry.ACCESS }, expiryTime = Expire.ONE_MINUTE) public void oldest_partial(CacheContext context, @ExpireAfterAccess Expiration expireAfterAccess) { int count = (int) context.initialSize() / 2; @@ -268,6 +292,8 @@ public void oldest_partial(CacheContext context, @Test(dataProvider = "caches") @CacheSpec(implementation = Implementation.Caffeine, population = {Population.PARTIAL, Population.FULL}, expireAfterAccess = Expire.ONE_MINUTE, + mustExpiresWithAnyOf = { AFTER_ACCESS, VARIABLE }, expiryTime = Expire.ONE_MINUTE, + expiry = { CacheExpiry.DISABLED, CacheExpiry.ACCESS }, removalListener = { Listener.DEFAULT, Listener.REJECTING }) public void oldest_order(CacheContext context, @ExpireAfterAccess Expiration expireAfterAccess) { @@ -276,7 +302,9 @@ public void oldest_order(CacheContext context, } @Test(dataProvider = "caches") - @CacheSpec(implementation = Implementation.Caffeine, expireAfterAccess = Expire.ONE_MINUTE) + @CacheSpec(implementation = Implementation.Caffeine, expireAfterAccess = Expire.ONE_MINUTE, + mustExpiresWithAnyOf = { AFTER_ACCESS, VARIABLE }, expiryTime = Expire.ONE_MINUTE, + expiry = { CacheExpiry.DISABLED, CacheExpiry.ACCESS }) public void oldest_snapshot(Cache cache, CacheContext context, @ExpireAfterAccess Expiration expireAfterAccess) { Map oldest = expireAfterAccess.oldest(Integer.MAX_VALUE); @@ -286,14 +314,18 @@ public void oldest_snapshot(Cache cache, CacheContext context, /* ---------------- Policy: youngest -------------- */ - @CacheSpec(implementation = Implementation.Caffeine, expireAfterAccess = Expire.ONE_MINUTE) + @CacheSpec(implementation = Implementation.Caffeine, expireAfterAccess = Expire.ONE_MINUTE, + mustExpiresWithAnyOf = { AFTER_ACCESS, VARIABLE }, expiryTime = Expire.ONE_MINUTE, + expiry = { CacheExpiry.DISABLED, CacheExpiry.ACCESS }) @Test(dataProvider = "caches", expectedExceptions = UnsupportedOperationException.class) public void youngest_unmodifiable(CacheContext context, @ExpireAfterAccess Expiration expireAfterAccess) { expireAfterAccess.youngest(Integer.MAX_VALUE).clear();; } - @CacheSpec(implementation = Implementation.Caffeine, expireAfterAccess = Expire.ONE_MINUTE) + @CacheSpec(implementation = Implementation.Caffeine, expireAfterAccess = Expire.ONE_MINUTE, + mustExpiresWithAnyOf = { AFTER_ACCESS, VARIABLE }, expiryTime = Expire.ONE_MINUTE, + expiry = { CacheExpiry.DISABLED, CacheExpiry.ACCESS }) @Test(dataProvider = "caches", expectedExceptions = IllegalArgumentException.class) public void youngest_negative(CacheContext context, @ExpireAfterAccess Expiration expireAfterAccess) { @@ -301,15 +333,18 @@ public void youngest_negative(CacheContext context, } @Test(dataProvider = "caches") - @CacheSpec(implementation = Implementation.Caffeine, expireAfterAccess = Expire.ONE_MINUTE) + @CacheSpec(implementation = Implementation.Caffeine, expireAfterAccess = Expire.ONE_MINUTE, + mustExpiresWithAnyOf = { AFTER_ACCESS, VARIABLE }, expiryTime = Expire.ONE_MINUTE, + expiry = { CacheExpiry.DISABLED, CacheExpiry.ACCESS }) public void youngest_zero(CacheContext context, @ExpireAfterAccess Expiration expireAfterAccess) { assertThat(expireAfterAccess.youngest(0), is(emptyMap())); } @Test(dataProvider = "caches") - @CacheSpec(implementation = Implementation.Caffeine, - population = Population.FULL, expireAfterAccess = Expire.ONE_MINUTE) + @CacheSpec(implementation = Implementation.Caffeine, population = Population.FULL, + mustExpiresWithAnyOf = { AFTER_ACCESS, VARIABLE }, expiryTime = Expire.ONE_MINUTE, + expiry = { CacheExpiry.DISABLED, CacheExpiry.ACCESS }, expireAfterAccess = Expire.ONE_MINUTE) public void youngest_partial(CacheContext context, @ExpireAfterAccess Expiration expireAfterAccess) { int count = (int) context.initialSize() / 2; @@ -318,7 +353,9 @@ public void youngest_partial(CacheContext context, @Test(dataProvider = "caches") @CacheSpec(implementation = Implementation.Caffeine, - population = {Population.PARTIAL, Population.FULL}, expireAfterAccess = Expire.ONE_MINUTE, + population = {Population.PARTIAL, Population.FULL}, + mustExpiresWithAnyOf = { AFTER_ACCESS, VARIABLE }, expireAfterAccess = Expire.ONE_MINUTE, + expiry = { CacheExpiry.DISABLED, CacheExpiry.ACCESS }, expiryTime = Expire.ONE_MINUTE, removalListener = { Listener.DEFAULT, Listener.REJECTING }) public void youngest_order(CacheContext context, @ExpireAfterAccess Expiration expireAfterAccess) { @@ -328,7 +365,9 @@ public void youngest_order(CacheContext context, } @Test(dataProvider = "caches") - @CacheSpec(implementation = Implementation.Caffeine, expireAfterAccess = Expire.ONE_MINUTE) + @CacheSpec(implementation = Implementation.Caffeine, expireAfterAccess = Expire.ONE_MINUTE, + mustExpiresWithAnyOf = { AFTER_ACCESS, VARIABLE }, expiryTime = Expire.ONE_MINUTE, + expiry = { CacheExpiry.DISABLED, CacheExpiry.ACCESS }) public void youngest_snapshot(Cache cache, CacheContext context, @ExpireAfterAccess Expiration expireAfterAccess) { Map youngest = expireAfterAccess.youngest(Integer.MAX_VALUE); diff --git a/caffeine/src/test/java/com/github/benmanes/caffeine/cache/ExpireAfterWriteTest.java b/caffeine/src/test/java/com/github/benmanes/caffeine/cache/ExpireAfterWriteTest.java index e037339ac6..a15fb82cc3 100644 --- a/caffeine/src/test/java/com/github/benmanes/caffeine/cache/ExpireAfterWriteTest.java +++ b/caffeine/src/test/java/com/github/benmanes/caffeine/cache/ExpireAfterWriteTest.java @@ -15,6 +15,8 @@ */ package com.github.benmanes.caffeine.cache; +import static com.github.benmanes.caffeine.cache.testing.CacheSpec.Expiration.AFTER_WRITE; +import static com.github.benmanes.caffeine.cache.testing.CacheSpec.Expiration.VARIABLE; 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; @@ -38,6 +40,7 @@ import com.github.benmanes.caffeine.cache.testing.CacheContext; import com.github.benmanes.caffeine.cache.testing.CacheProvider; import com.github.benmanes.caffeine.cache.testing.CacheSpec; +import com.github.benmanes.caffeine.cache.testing.CacheSpec.CacheExpiry; import com.github.benmanes.caffeine.cache.testing.CacheSpec.Expire; import com.github.benmanes.caffeine.cache.testing.CacheSpec.Implementation; import com.github.benmanes.caffeine.cache.testing.CacheSpec.Listener; @@ -61,7 +64,9 @@ public final class ExpireAfterWriteTest { /* ---------------- Cache -------------- */ @Test(dataProvider = "caches") - @CacheSpec(expireAfterWrite = Expire.ONE_MINUTE, + @CacheSpec(mustExpiresWithAnyOf = { AFTER_WRITE, VARIABLE }, + expireAfterWrite = { Expire.DISABLED, Expire.ONE_MINUTE }, + expiry = { CacheExpiry.DISABLED, CacheExpiry.WRITE }, expiryTime = Expire.ONE_MINUTE, population = { Population.PARTIAL, Population.FULL }) public void getIfPresent(Cache cache, CacheContext context) { context.ticker().advance(30, TimeUnit.SECONDS); @@ -78,8 +83,9 @@ public void getIfPresent(Cache cache, CacheContext context) { } @Test(dataProvider = "caches") - @CacheSpec(expireAfterWrite = Expire.ONE_MINUTE, - population = { Population.PARTIAL, Population.FULL }) + @CacheSpec(population = { Population.PARTIAL, Population.FULL }, + mustExpiresWithAnyOf = { AFTER_WRITE, VARIABLE }, expireAfterWrite = Expire.ONE_MINUTE, + expiry = { CacheExpiry.DISABLED, CacheExpiry.WRITE }, expiryTime = Expire.ONE_MINUTE) public void get(Cache cache, CacheContext context) { Function mappingFunction = context.original()::get; context.ticker().advance(30, TimeUnit.SECONDS); @@ -96,8 +102,9 @@ public void get(Cache cache, CacheContext context) { } @Test(dataProvider = "caches") - @CacheSpec(expireAfterWrite = Expire.ONE_MINUTE, - population = { Population.PARTIAL, Population.FULL }) + @CacheSpec(population = { Population.PARTIAL, Population.FULL }, + mustExpiresWithAnyOf = { AFTER_WRITE, VARIABLE }, expireAfterWrite = Expire.ONE_MINUTE, + expiry = { CacheExpiry.DISABLED, CacheExpiry.WRITE }, expiryTime = Expire.ONE_MINUTE) public void getAllPresent(Cache cache, CacheContext context) { context.ticker().advance(30, TimeUnit.SECONDS); cache.getAllPresent(context.firstMiddleLastKeys()); @@ -115,8 +122,9 @@ public void getAllPresent(Cache cache, CacheContext context) { /* ---------------- LoadingCache -------------- */ @Test(dataProvider = "caches") - @CacheSpec(expireAfterWrite = Expire.ONE_MINUTE, - population = { Population.PARTIAL, Population.FULL }) + @CacheSpec(population = { Population.PARTIAL, Population.FULL }, + mustExpiresWithAnyOf = { AFTER_WRITE, VARIABLE }, expireAfterWrite = Expire.ONE_MINUTE, + expiry = { CacheExpiry.DISABLED, CacheExpiry.WRITE }, expiryTime = Expire.ONE_MINUTE) public void get(LoadingCache cache, CacheContext context) { context.ticker().advance(30, TimeUnit.SECONDS); cache.get(context.firstKey()); @@ -132,8 +140,10 @@ public void get(LoadingCache cache, CacheContext context) { } @Test(dataProvider = "caches") - @CacheSpec(expireAfterWrite = Expire.ONE_MINUTE, loader = {Loader.IDENTITY, Loader.BULK_IDENTITY}, - population = { Population.PARTIAL, Population.FULL }) + @CacheSpec(population = { Population.PARTIAL, Population.FULL }, + mustExpiresWithAnyOf = { AFTER_WRITE, VARIABLE }, expireAfterWrite = Expire.ONE_MINUTE, + expiry = { CacheExpiry.DISABLED, CacheExpiry.WRITE }, expiryTime = Expire.ONE_MINUTE, + loader = {Loader.IDENTITY, Loader.BULK_IDENTITY}) public void getAll(LoadingCache cache, CacheContext context) { context.ticker().advance(30, TimeUnit.SECONDS); assertThat(cache.getAll(ImmutableList.of(context.firstKey(), context.absentKey())), @@ -155,8 +165,9 @@ public void getAll(LoadingCache cache, CacheContext context) { /* ---------------- AsyncLoadingCache -------------- */ @Test(dataProvider = "caches") - @CacheSpec(expireAfterWrite = Expire.ONE_MINUTE, - population = { Population.PARTIAL, Population.FULL }) + @CacheSpec(population = { Population.PARTIAL, Population.FULL }, + mustExpiresWithAnyOf = { AFTER_WRITE, VARIABLE }, expireAfterWrite = Expire.ONE_MINUTE, + expiry = { CacheExpiry.DISABLED, CacheExpiry.WRITE }, expiryTime = Expire.ONE_MINUTE) @SuppressWarnings("FutureReturnValueIgnored") public void getIfPresent(AsyncLoadingCache cache, CacheContext context) { context.ticker().advance(30, TimeUnit.SECONDS); @@ -173,7 +184,9 @@ public void getIfPresent(AsyncLoadingCache cache, CacheContext /* ---------------- Map -------------- */ @Test(dataProvider = "caches") - @CacheSpec(expireAfterWrite = Expire.ONE_MINUTE, population = Population.FULL) + @CacheSpec(population = Population.FULL, + mustExpiresWithAnyOf = { AFTER_WRITE, VARIABLE }, expireAfterWrite = Expire.ONE_MINUTE, + expiry = { CacheExpiry.DISABLED, CacheExpiry.WRITE }, expiryTime = Expire.ONE_MINUTE) public void putIfAbsent(Map map, CacheContext context) { context.ticker().advance(30, TimeUnit.SECONDS); assertThat(map.putIfAbsent(context.firstKey(), context.absentValue()), is(not(nullValue()))); @@ -190,14 +203,18 @@ public void putIfAbsent(Map map, CacheContext context) { /* ---------------- Policy -------------- */ @Test(dataProvider = "caches") - @CacheSpec(implementation = Implementation.Caffeine, expireAfterWrite = Expire.ONE_MINUTE) + @CacheSpec(implementation = Implementation.Caffeine, + mustExpiresWithAnyOf = { AFTER_WRITE, VARIABLE }, expireAfterWrite = Expire.ONE_MINUTE, + expiry = { CacheExpiry.DISABLED, CacheExpiry.WRITE }, expiryTime = Expire.ONE_MINUTE) public void getExpiresAfter(CacheContext context, @ExpireAfterWrite Expiration expireAfterWrite) { assertThat(expireAfterWrite.getExpiresAfter(TimeUnit.MINUTES), is(1L)); } @Test(dataProvider = "caches") - @CacheSpec(implementation = Implementation.Caffeine, expireAfterWrite = Expire.ONE_MINUTE) + @CacheSpec(implementation = Implementation.Caffeine, + mustExpiresWithAnyOf = { AFTER_WRITE, VARIABLE }, expireAfterWrite = Expire.ONE_MINUTE, + expiry = { CacheExpiry.DISABLED, CacheExpiry.WRITE }, expiryTime = Expire.ONE_MINUTE) public void setExpiresAfter(Cache cache, CacheContext context, @ExpireAfterWrite Expiration expireAfterWrite) { expireAfterWrite.setExpiresAfter(2, TimeUnit.MINUTES); @@ -209,7 +226,9 @@ public void setExpiresAfter(Cache cache, CacheContext context, } @Test(dataProvider = "caches") - @CacheSpec(implementation = Implementation.Caffeine, expireAfterWrite = Expire.ONE_MINUTE, + @CacheSpec(implementation = Implementation.Caffeine, + mustExpiresWithAnyOf = { AFTER_WRITE, VARIABLE }, expireAfterWrite = Expire.ONE_MINUTE, + expiry = { CacheExpiry.DISABLED, CacheExpiry.WRITE }, expiryTime = Expire.ONE_MINUTE, population = { Population.SINGLETON, Population.PARTIAL, Population.FULL }) public void ageOf(CacheContext context, @ExpireAfterWrite Expiration expireAfterWrite) { @@ -222,14 +241,18 @@ public void ageOf(CacheContext context, /* ---------------- Policy: oldest -------------- */ - @CacheSpec(implementation = Implementation.Caffeine, expireAfterWrite = Expire.ONE_MINUTE) + @CacheSpec(implementation = Implementation.Caffeine, + mustExpiresWithAnyOf = { AFTER_WRITE, VARIABLE }, expireAfterWrite = Expire.ONE_MINUTE, + expiry = { CacheExpiry.DISABLED, CacheExpiry.WRITE }, expiryTime = Expire.ONE_MINUTE) @Test(dataProvider = "caches", expectedExceptions = UnsupportedOperationException.class) public void oldest_unmodifiable(CacheContext context, @ExpireAfterWrite Expiration expireAfterWrite) { expireAfterWrite.oldest(Integer.MAX_VALUE).clear();; } - @CacheSpec(implementation = Implementation.Caffeine, expireAfterWrite = Expire.ONE_MINUTE) + @CacheSpec(implementation = Implementation.Caffeine, + mustExpiresWithAnyOf = { AFTER_WRITE, VARIABLE }, expireAfterWrite = Expire.ONE_MINUTE, + expiry = { CacheExpiry.DISABLED, CacheExpiry.WRITE }, expiryTime = Expire.ONE_MINUTE) @Test(dataProvider = "caches", expectedExceptions = IllegalArgumentException.class) public void oldest_negative(CacheContext context, @ExpireAfterWrite Expiration expireAfterWrite) { @@ -237,15 +260,18 @@ public void oldest_negative(CacheContext context, } @Test(dataProvider = "caches") - @CacheSpec(implementation = Implementation.Caffeine, expireAfterWrite = Expire.ONE_MINUTE) + @CacheSpec(implementation = Implementation.Caffeine, + mustExpiresWithAnyOf = { AFTER_WRITE, VARIABLE }, expireAfterWrite = Expire.ONE_MINUTE, + expiry = { CacheExpiry.DISABLED, CacheExpiry.WRITE }, expiryTime = Expire.ONE_MINUTE) public void oldest_zero(CacheContext context, @ExpireAfterWrite Expiration expireAfterWrite) { assertThat(expireAfterWrite.oldest(0), is(emptyMap())); } @Test(dataProvider = "caches") - @CacheSpec(implementation = Implementation.Caffeine, - population = Population.FULL, expireAfterWrite = Expire.ONE_MINUTE) + @CacheSpec(implementation = Implementation.Caffeine, population = Population.FULL, + mustExpiresWithAnyOf = { AFTER_WRITE, VARIABLE }, expireAfterWrite = Expire.ONE_MINUTE, + expiry = { CacheExpiry.DISABLED, CacheExpiry.WRITE }, expiryTime = Expire.ONE_MINUTE) public void oldest_partial(CacheContext context, @ExpireAfterWrite Expiration expireAfterWrite) { int count = (int) context.initialSize() / 2; @@ -254,8 +280,10 @@ public void oldest_partial(CacheContext context, @Test(dataProvider = "caches") @CacheSpec(implementation = Implementation.Caffeine, - population = {Population.PARTIAL, Population.FULL}, expireAfterWrite = Expire.ONE_MINUTE, - removalListener = { Listener.DEFAULT, Listener.REJECTING }) + population = {Population.PARTIAL, Population.FULL}, + removalListener = { Listener.DEFAULT, Listener.REJECTING }, + mustExpiresWithAnyOf = { AFTER_WRITE, VARIABLE }, expireAfterWrite = Expire.ONE_MINUTE, + expiry = { CacheExpiry.DISABLED, CacheExpiry.WRITE }, expiryTime = Expire.ONE_MINUTE) public void oldest_order(CacheContext context, @ExpireAfterWrite Expiration expireAfterWrite) { Map oldest = expireAfterWrite.oldest(Integer.MAX_VALUE); @@ -263,7 +291,9 @@ public void oldest_order(CacheContext context, } @Test(dataProvider = "caches") - @CacheSpec(implementation = Implementation.Caffeine, expireAfterWrite = Expire.ONE_MINUTE) + @CacheSpec(implementation = Implementation.Caffeine, + mustExpiresWithAnyOf = { AFTER_WRITE, VARIABLE }, expireAfterWrite = Expire.ONE_MINUTE, + expiry = { CacheExpiry.DISABLED, CacheExpiry.WRITE }, expiryTime = Expire.ONE_MINUTE) public void oldest_snapshot(Cache cache, CacheContext context, @ExpireAfterWrite Expiration expireAfterWrite) { Map oldest = expireAfterWrite.oldest(Integer.MAX_VALUE); @@ -273,14 +303,18 @@ public void oldest_snapshot(Cache cache, CacheContext context, /* ---------------- Policy: youngest -------------- */ - @CacheSpec(implementation = Implementation.Caffeine, expireAfterWrite = Expire.ONE_MINUTE) + @CacheSpec(implementation = Implementation.Caffeine, + mustExpiresWithAnyOf = { AFTER_WRITE, VARIABLE }, expireAfterWrite = Expire.ONE_MINUTE, + expiry = { CacheExpiry.DISABLED, CacheExpiry.WRITE }, expiryTime = Expire.ONE_MINUTE) @Test(dataProvider = "caches", expectedExceptions = UnsupportedOperationException.class) public void youngest_unmodifiable(CacheContext context, @ExpireAfterWrite Expiration expireAfterWrite) { expireAfterWrite.youngest(Integer.MAX_VALUE).clear();; } - @CacheSpec(implementation = Implementation.Caffeine, expireAfterWrite = Expire.ONE_MINUTE) + @CacheSpec(implementation = Implementation.Caffeine, + mustExpiresWithAnyOf = { AFTER_WRITE, VARIABLE }, expireAfterWrite = Expire.ONE_MINUTE, + expiry = { CacheExpiry.DISABLED, CacheExpiry.WRITE }, expiryTime = Expire.ONE_MINUTE) @Test(dataProvider = "caches", expectedExceptions = IllegalArgumentException.class) public void youngest_negative(CacheContext context, @ExpireAfterWrite Expiration expireAfterWrite) { @@ -288,15 +322,18 @@ public void youngest_negative(CacheContext context, } @Test(dataProvider = "caches") - @CacheSpec(implementation = Implementation.Caffeine, expireAfterWrite = Expire.ONE_MINUTE) + @CacheSpec(implementation = Implementation.Caffeine, + mustExpiresWithAnyOf = { AFTER_WRITE, VARIABLE }, expireAfterWrite = Expire.ONE_MINUTE, + expiry = { CacheExpiry.DISABLED, CacheExpiry.WRITE }, expiryTime = Expire.ONE_MINUTE) public void youngest_zero(CacheContext context, @ExpireAfterWrite Expiration expireAfterWrite) { assertThat(expireAfterWrite.youngest(0), is(emptyMap())); } @Test(dataProvider = "caches") - @CacheSpec(implementation = Implementation.Caffeine, - population = Population.FULL, expireAfterWrite = Expire.ONE_MINUTE) + @CacheSpec(implementation = Implementation.Caffeine, population = Population.FULL, + mustExpiresWithAnyOf = { AFTER_WRITE, VARIABLE }, expireAfterWrite = Expire.ONE_MINUTE, + expiry = { CacheExpiry.DISABLED, CacheExpiry.WRITE }, expiryTime = Expire.ONE_MINUTE) public void youngest_partial(CacheContext context, @ExpireAfterWrite Expiration expireAfterWrite) { int count = (int) context.initialSize() / 2; @@ -305,8 +342,10 @@ public void youngest_partial(CacheContext context, @Test(dataProvider = "caches") @CacheSpec(implementation = Implementation.Caffeine, - population = {Population.PARTIAL, Population.FULL}, expireAfterWrite = Expire.ONE_MINUTE, - removalListener = { Listener.DEFAULT, Listener.REJECTING }) + population = {Population.PARTIAL, Population.FULL}, + removalListener = { Listener.DEFAULT, Listener.REJECTING }, + mustExpiresWithAnyOf = { AFTER_WRITE, VARIABLE }, expireAfterWrite = Expire.ONE_MINUTE, + expiry = { CacheExpiry.DISABLED, CacheExpiry.WRITE }, expiryTime = Expire.ONE_MINUTE) public void youngest_order(CacheContext context, @ExpireAfterWrite Expiration expireAfterWrite) { Map youngest = expireAfterWrite.youngest(Integer.MAX_VALUE); @@ -315,7 +354,9 @@ public void youngest_order(CacheContext context, } @Test(dataProvider = "caches") - @CacheSpec(implementation = Implementation.Caffeine, expireAfterWrite = Expire.ONE_MINUTE) + @CacheSpec(implementation = Implementation.Caffeine, + mustExpiresWithAnyOf = { AFTER_WRITE, VARIABLE }, expireAfterWrite = Expire.ONE_MINUTE, + expiry = { CacheExpiry.DISABLED, CacheExpiry.WRITE }, expiryTime = Expire.ONE_MINUTE) public void youngest_snapshot(Cache cache, CacheContext context, @ExpireAfterWrite Expiration expireAfterWrite) { Map youngest = expireAfterWrite.youngest(Integer.MAX_VALUE); diff --git a/caffeine/src/test/java/com/github/benmanes/caffeine/cache/IsCacheReserializable.java b/caffeine/src/test/java/com/github/benmanes/caffeine/cache/IsCacheReserializable.java index 4b0d98c2e3..a018701357 100644 --- a/caffeine/src/test/java/com/github/benmanes/caffeine/cache/IsCacheReserializable.java +++ b/caffeine/src/test/java/com/github/benmanes/caffeine/cache/IsCacheReserializable.java @@ -23,6 +23,7 @@ import org.hamcrest.Matcher; import org.hamcrest.TypeSafeDiagnosingMatcher; +import com.github.benmanes.caffeine.cache.Async.AsyncExpiry; import com.github.benmanes.caffeine.cache.Async.AsyncWeigher; import com.github.benmanes.caffeine.testing.DescriptionBuilder; import com.google.common.testing.SerializableTester; @@ -209,15 +210,17 @@ private static void checkBoundedAsyncLocalLoadingCache( private static void checkBoundedLocalCache(BoundedLocalCache original, BoundedLocalCache copy, DescriptionBuilder desc) { desc.expectThat("empty", copy.estimatedSize(), is(0L)); - desc.expectThat("same weigher", - unwrapWeigher(copy.weigher).getClass(), is(equalTo( - unwrapWeigher(original.weigher).getClass()))); + desc.expectThat("same weigher", unwrapWeigher(copy.weigher).getClass(), + is(equalTo(unwrapWeigher(original.weigher).getClass()))); desc.expectThat("same nodeFactory", copy.nodeFactory, is(original.nodeFactory)); if (original.evicts()) { desc.expectThat("same maximumWeight", copy.maximum(), is(original.maximum())); desc.expectThat("same maximumEdenWeight", copy.edenMaximum(), is(original.edenMaximum())); } + desc.expectThat("same expiry", unwrapExpiry(copy.expiry()).getClass(), + is(equalTo(unwrapExpiry(original.expiry()).getClass()))); + if (original.expiresAfterAccess()) { desc.expectThat("same expiresAfterAccessNanos", copy.expiresAfterAccessNanos(), is(original.expiresAfterAccessNanos())); @@ -248,18 +251,26 @@ private static void checkBoundedLocalCache(BoundedLocalCache origin } } - /* ---------------- Shared -------------- */ - - private static Weigher unwrapWeigher(Weigher weigher) { + @SuppressWarnings("unchecked") + private static Weigher unwrapWeigher(Weigher weigher) { for (;;) { if (weigher instanceof BoundedWeigher) { - weigher = ((BoundedWeigher) weigher).delegate; + weigher = (Weigher) ((BoundedWeigher) weigher).delegate; } else if (weigher instanceof AsyncWeigher) { - weigher = ((AsyncWeigher) weigher).delegate; + weigher = (Weigher) ((AsyncWeigher) weigher).delegate; + } else { + return weigher; + } + } + } + + @SuppressWarnings("unchecked") + private static Expiry unwrapExpiry(Expiry expiry) { + for (;;) { + if (expiry instanceof AsyncExpiry) { + expiry = (Expiry) ((AsyncExpiry) expiry).delegate; } else { - @SuppressWarnings("unchecked") - Weigher castedWeigher = (Weigher) weigher; - return castedWeigher; + return expiry; } } } diff --git a/caffeine/src/test/java/com/github/benmanes/caffeine/cache/TimerWheelTest.java b/caffeine/src/test/java/com/github/benmanes/caffeine/cache/TimerWheelTest.java index 5b09553091..f48e12f2c1 100644 --- a/caffeine/src/test/java/com/github/benmanes/caffeine/cache/TimerWheelTest.java +++ b/caffeine/src/test/java/com/github/benmanes/caffeine/cache/TimerWheelTest.java @@ -72,7 +72,7 @@ public void schedule(long nanos, int expired) { verify(cache, times(expired)).evictEntry(any(), any(), anyLong()); for (Node node : captor.getAllValues()) { - assertThat(node.getAccessTime(), is(lessThan(nanos))); + assertThat(node.getVariableTime(), is(lessThan(nanos))); } } @@ -101,7 +101,7 @@ public void schedule_fuzzy(long clock, long nanos, long[] times) { verify(cache, times(expired)).evictEntry(any(), any(), anyLong()); for (Node node : captor.getAllValues()) { - assertThat(node.getAccessTime(), is(lessThan(nanos))); + assertThat(node.getVariableTime(), is(lessThan(nanos))); } checkTimerWheel(nanos); } @@ -133,9 +133,9 @@ private void checkTimerWheel(long nanos) { private LongList getTimers(Node sentinel) { LongList timers = new LongArrayList(); - for (Node node = sentinel.getNextInAccessOrder(); - node != sentinel; node = node.getNextInAccessOrder()) { - timers.add(node.getAccessTime()); + for (Node node = sentinel.getNextInVariableOrder(); + node != sentinel; node = node.getNextInVariableOrder()) { + timers.add(node.getVariableTime()); } return timers; } @@ -146,11 +146,11 @@ public void reschedule() { Timer timer = new Timer(TimeUnit.MINUTES.toNanos(15)); timerWheel.schedule(timer); - Node startBucket = timer.getNextInAccessOrder(); + Node startBucket = timer.getNextInVariableOrder(); - timer.setAccessTime(TimeUnit.HOURS.toNanos(2)); + timer.setVariableTime(TimeUnit.HOURS.toNanos(2)); timerWheel.reschedule(timer); - assertThat(timer.getNextInAccessOrder(), is(not(startBucket))); + assertThat(timer.getNextInVariableOrder(), is(not(startBucket))); timerWheel.advance(TimeUnit.DAYS.toNanos(1)); checkEmpty(); @@ -160,8 +160,8 @@ private void checkEmpty() { for (int i = 0; i < timerWheel.wheel.length; i++) { for (int j = 0; j < timerWheel.wheel[i].length; j++) { Node sentinel = timerWheel.wheel[i][j]; - assertThat(sentinel.getNextInAccessOrder(), is(sentinel)); - assertThat(sentinel.getPreviousInAccessOrder(), is(sentinel)); + assertThat(sentinel.getNextInVariableOrder(), is(sentinel)); + assertThat(sentinel.getPreviousInVariableOrder(), is(sentinel)); } } } @@ -171,8 +171,8 @@ public void deschedule() { Timer timer = new Timer(100); timerWheel.schedule(timer); timerWheel.deschedule(timer); - assertThat(timer.getNextInAccessOrder(), is(nullValue())); - assertThat(timer.getPreviousInAccessOrder(), is(nullValue())); + assertThat(timer.getNextInVariableOrder(), is(nullValue())); + assertThat(timer.getPreviousInVariableOrder(), is(nullValue())); } @Test @@ -200,7 +200,7 @@ public void deschedule_fuzzy(long clock, long nanos, long[] times) { public void expire_reschedule() { when(cache.evictEntry(captor.capture(), any(), anyLong())).thenAnswer(invocation -> { Timer timer = (Timer) invocation.getArgument(0); - timer.setAccessTime(timerWheel.nanos + 100); + timer.setVariableTime(timerWheel.nanos + 100); return false; }); @@ -208,8 +208,8 @@ public void expire_reschedule() { timerWheel.advance(TimerWheel.SPANS[0]); verify(cache).evictEntry(any(), any(), anyLong()); - assertThat(captor.getValue().getNextInAccessOrder(), is(not(nullValue()))); - assertThat(captor.getValue().getPreviousInAccessOrder(), is(not(nullValue()))); + assertThat(captor.getValue().getNextInVariableOrder(), is(not(nullValue()))); + assertThat(captor.getValue().getPreviousInVariableOrder(), is(not(nullValue()))); } @Test(dataProvider = "cascade") @@ -241,28 +241,28 @@ public Iterator providesCascade() { private static final class Timer implements Node { Node prev; Node next; - long accessTime; + long variableTime; Timer(long accessTime) { - setAccessTime(accessTime); + setVariableTime(accessTime); } - @Override public long getAccessTime() { - return accessTime; + @Override public long getVariableTime() { + return variableTime; } - @Override public void setAccessTime(long accessTime) { - this.accessTime = accessTime; + @Override public void setVariableTime(long variableTime) { + this.variableTime = variableTime; } - @Override public Node getPreviousInAccessOrder() { + @Override public Node getPreviousInVariableOrder() { return prev; } - @Override public void setPreviousInAccessOrder(@Nullable Node prev) { + @Override public void setPreviousInVariableOrder(@Nullable Node prev) { this.prev = prev; } - @Override public Node getNextInAccessOrder() { + @Override public Node getNextInVariableOrder() { return next; } - @Override public void setNextInAccessOrder(@Nullable Node next) { + @Override public void setNextInVariableOrder(@Nullable Node next) { this.next = next; } diff --git a/caffeine/src/test/java/com/github/benmanes/caffeine/cache/testing/CacheContext.java b/caffeine/src/test/java/com/github/benmanes/caffeine/cache/testing/CacheContext.java index 5daf004338..e802456107 100644 --- a/caffeine/src/test/java/com/github/benmanes/caffeine/cache/testing/CacheContext.java +++ b/caffeine/src/test/java/com/github/benmanes/caffeine/cache/testing/CacheContext.java @@ -39,13 +39,16 @@ import com.github.benmanes.caffeine.cache.CacheLoader; import com.github.benmanes.caffeine.cache.CacheWriter; import com.github.benmanes.caffeine.cache.Caffeine; +import com.github.benmanes.caffeine.cache.Expiry; import com.github.benmanes.caffeine.cache.LoadingCache; import com.github.benmanes.caffeine.cache.RemovalListener; import com.github.benmanes.caffeine.cache.stats.CacheStats; import com.github.benmanes.caffeine.cache.testing.CacheSpec.Advance; import com.github.benmanes.caffeine.cache.testing.CacheSpec.CacheExecutor; +import com.github.benmanes.caffeine.cache.testing.CacheSpec.CacheExpiry; import com.github.benmanes.caffeine.cache.testing.CacheSpec.CacheWeigher; import com.github.benmanes.caffeine.cache.testing.CacheSpec.Compute; +import com.github.benmanes.caffeine.cache.testing.CacheSpec.Expiration; import com.github.benmanes.caffeine.cache.testing.CacheSpec.Expire; import com.github.benmanes.caffeine.cache.testing.CacheSpec.Implementation; import com.github.benmanes.caffeine.cache.testing.CacheSpec.InitialCapacity; @@ -73,17 +76,20 @@ public final class CacheContext { final RemovalListener removalListener; final CacheWriter cacheWriter; final InitialCapacity initialCapacity; + final Expiry expiry; final Map original; final Implementation implementation; final Listener removalListenerType; final CacheExecutor cacheExecutor; final ReferenceType valueStrength; final ReferenceType keyStrength; + final CacheExpiry expiryType; final Population population; final CacheWeigher weigher; final Maximum maximumSize; final Expire afterAccess; final Expire afterWrite; + final Expire expiryTime; final Executor executor; final FakeTicker ticker; final Compute compute; @@ -112,11 +118,11 @@ public final class CacheContext { Map absent; public CacheContext(InitialCapacity initialCapacity, Stats stats, CacheWeigher weigher, - Maximum maximumSize, Expire afterAccess, Expire afterWrite, Expire refresh, - Advance advance, ReferenceType keyStrength, ReferenceType valueStrength, + Maximum maximumSize, CacheExpiry expiryType, Expire afterAccess, Expire afterWrite, + Expire refresh, Advance advance, ReferenceType keyStrength, ReferenceType valueStrength, CacheExecutor cacheExecutor, Listener removalListenerType, Population population, boolean isLoading, boolean isAsyncLoading, Compute compute, Loader loader, Writer writer, - Implementation implementation) { + Implementation implementation, CacheSpec cacheSpec) { this.initialCapacity = requireNonNull(initialCapacity); this.stats = requireNonNull(stats); this.weigher = requireNonNull(weigher); @@ -128,19 +134,22 @@ public CacheContext(InitialCapacity initialCapacity, Stats stats, CacheWeigher w this.keyStrength = requireNonNull(keyStrength); this.valueStrength = requireNonNull(valueStrength); this.cacheExecutor = requireNonNull(cacheExecutor); - this.executor = cacheExecutor.get(); + this.executor = cacheExecutor.create(); this.removalListenerType = removalListenerType; this.removalListener = removalListenerType.create(); this.population = requireNonNull(population); this.loader = isLoading ? requireNonNull(loader) : null; this.isAsyncLoading = isAsyncLoading; this.writer = requireNonNull(writer); - this.cacheWriter = writer.get(); + this.cacheWriter = writer.create(); this.ticker = new SerializableFakeTicker(); this.implementation = requireNonNull(implementation); this.original = new LinkedHashMap<>(); this.initialSize = -1; this.compute = compute; + this.expiryType = expiryType; + this.expiryTime = cacheSpec.expiryTime(); + this.expiry = expiryType.createExpiry(expiryTime); } public InitialCapacity initialCapacity() { @@ -355,8 +364,34 @@ public CacheStats stats() { return cache.stats(); } + public boolean expires(Expiration expiration) { + return (expiresAfterAccess() && (expiration == Expiration.AFTER_ACCESS)) + || (expiresAfterWrite() && (expiration == Expiration.AFTER_WRITE)) + || (expiresVariably() && (expiration == Expiration.VARIABLE)); + } + public boolean expires() { - return (afterAccess != Expire.DISABLED) || (afterWrite != Expire.DISABLED); + return expiresVariably() || expiresAfterAccess() || expiresAfterWrite(); + } + + public boolean expiresAfterAccess() { + return (afterAccess != Expire.DISABLED); + } + + public boolean expiresAfterWrite() { + return (afterWrite != Expire.DISABLED); + } + + public boolean expiresVariably() { + return (expiryType != CacheExpiry.DISABLED); + } + + public CacheExpiry expiryType() { + return expiryType; + } + + public Expire expiryTime() { + return expiryTime; } public Expire expireAfterAccess() { @@ -424,6 +459,8 @@ public String toString() { .add("population", population) .add("maximumSize", maximumSize) .add("weigher", weigher) + .add("expiry", expiryType) + .add("expiryTime", expiryTime) .add("afterAccess", afterAccess) .add("afterWrite", afterWrite) .add("refreshAfterWrite", refresh) diff --git a/caffeine/src/test/java/com/github/benmanes/caffeine/cache/testing/CacheGenerator.java b/caffeine/src/test/java/com/github/benmanes/caffeine/cache/testing/CacheGenerator.java index ec1836f5dc..3c92f3caf5 100644 --- a/caffeine/src/test/java/com/github/benmanes/caffeine/cache/testing/CacheGenerator.java +++ b/caffeine/src/test/java/com/github/benmanes/caffeine/cache/testing/CacheGenerator.java @@ -30,6 +30,7 @@ import com.github.benmanes.caffeine.cache.Cache; import com.github.benmanes.caffeine.cache.testing.CacheSpec.Advance; import com.github.benmanes.caffeine.cache.testing.CacheSpec.CacheExecutor; +import com.github.benmanes.caffeine.cache.testing.CacheSpec.CacheExpiry; import com.github.benmanes.caffeine.cache.testing.CacheSpec.CacheWeigher; import com.github.benmanes.caffeine.cache.testing.CacheSpec.Compute; import com.github.benmanes.caffeine.cache.testing.CacheSpec.Expire; @@ -115,6 +116,7 @@ private Set> combinations() { ImmutableSet.copyOf(statistics), ImmutableSet.copyOf(cacheSpec.weigher()), ImmutableSet.copyOf(cacheSpec.maximumSize()), + ImmutableSet.copyOf(cacheSpec.expiry()), ImmutableSet.copyOf(cacheSpec.expireAfterAccess()), ImmutableSet.copyOf(cacheSpec.expireAfterWrite()), ImmutableSet.copyOf(cacheSpec.refreshAfterWrite()), @@ -150,6 +152,7 @@ private CacheContext newCacheContext(List combination) { (Stats) combination.get(index++), (CacheWeigher) combination.get(index++), (Maximum) combination.get(index++), + (CacheExpiry) combination.get(index++), (Expire) combination.get(index++), (Expire) combination.get(index++), (Expire) combination.get(index++), @@ -164,7 +167,8 @@ private CacheContext newCacheContext(List combination) { (Compute) combination.get(index++), (Loader) combination.get(index++), (Writer) combination.get(index++), - (Implementation) combination.get(index++)); + (Implementation) combination.get(index++), + cacheSpec); } /** Returns if the context is a viable configuration. */ @@ -176,13 +180,19 @@ private boolean isCompatible(CacheContext context) { && (!context.isAsync() || !context.isLoading()); boolean refreshIncompatible = context.refreshes() && !context.isLoading(); boolean weigherIncompatible = context.isUnbounded() && context.isWeighted(); - boolean expirationIncompatible = cacheSpec.requiresExpiration() && !context.expires(); boolean referenceIncompatible = cacheSpec.requiresWeakOrSoft() && (context.isWeakKeys() || context.isWeakValues() || context.isSoftValues()); + boolean expiryIncompatible = (context.expiryType() != CacheExpiry.DISABLED) + && ((context.implementation() != Implementation.Caffeine) + || (context.expireAfterAccess() != Expire.DISABLED) + || (context.expireAfterWrite() != Expire.DISABLED)); + boolean expirationIncompatible = (cacheSpec.mustExpiresWithAnyOf().length > 0) + && !Arrays.stream(cacheSpec.mustExpiresWithAnyOf()).anyMatch(context::expires); boolean skip = asyncIncompatible || asyncLoaderIncompatible || refreshIncompatible || weigherIncompatible - || expirationIncompatible || referenceIncompatible; + || expiryIncompatible || expirationIncompatible + || referenceIncompatible; return !skip; } 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 e4230639c1..e7631af164 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 @@ -35,13 +35,13 @@ import java.util.concurrent.RejectedExecutionException; import java.util.concurrent.ThreadLocalRandom; import java.util.concurrent.TimeUnit; -import java.util.function.Supplier; import org.mockito.Mockito; import com.github.benmanes.caffeine.cache.AsyncCacheLoader; import com.github.benmanes.caffeine.cache.CacheLoader; import com.github.benmanes.caffeine.cache.CacheWriter; +import com.github.benmanes.caffeine.cache.Expiry; import com.github.benmanes.caffeine.cache.LoadingCache; import com.github.benmanes.caffeine.cache.RemovalListener; import com.github.benmanes.caffeine.cache.Weigher; @@ -225,8 +225,12 @@ public int unitsPerEntry() { /* ---------------- Expiration -------------- */ - /** Indicates that the combination must have an expiration setting. */ - boolean requiresExpiration() default false; + /** Indicates that the combination must have any of the expiration settings. */ + Expiration[] mustExpiresWithAnyOf() default {}; + + enum Expiration { + AFTER_WRITE, AFTER_ACCESS, VARIABLE + } /** The expiration time-to-idle setting, each resulting in a new combination. */ Expire[] expireAfterAccess() default { @@ -246,11 +250,54 @@ Expire[] refreshAfterWrite() default { Expire.FOREVER }; + /** The variable expiration setting, each resulting in a new combination. */ + CacheExpiry[] expiry() default { + CacheExpiry.DISABLED, + CacheExpiry.ACCESS + }; + + /** The fixed duration for the expiry. */ + Expire expiryTime() default Expire.FOREVER; + /** Indicates if the amount of time that should be auto-advance for each entry when populating. */ Advance[] advanceOnPopulation() default { Advance.ZERO }; + enum CacheExpiry { + DISABLED { + @Override public Expiry createExpiry(Expire expiryTime) { + return null; + } + }, + MOCKITO { + @Override public Expiry createExpiry(Expire expiryTime) { + @SuppressWarnings("unchecked") + Expiry mock = Mockito.mock(Expiry.class); + return mock; + } + }, + ACCESS { + @Override public Expiry createExpiry(Expire expiryTime) { + return ExpiryBuilder + .expiringAfterCreate(expiryTime.timeNanos()) + .expiringAfterUpdate(expiryTime.timeNanos()) + .expiringAfterRead(expiryTime.timeNanos()) + .build(); + } + }, + WRITE { + @Override public Expiry createExpiry(Expire expiryTime) { + return ExpiryBuilder + .expiringAfterCreate(expiryTime.timeNanos()) + .expiringAfterUpdate(expiryTime.timeNanos()) + .build(); + } + }; + + public abstract Expiry createExpiry(Expire expiryTime); + } + enum Expire { /** A flag indicating that entries are not evicted due to expiration. */ DISABLED(Long.MIN_VALUE), @@ -504,27 +551,29 @@ Writer[] writer() default { }; /** The {@link CacheWriter} for the external resource. */ - enum Writer implements Supplier> { + enum Writer { /** A writer that does nothing. */ DISABLED { - @Override public CacheWriter get() { + @Override public CacheWriter create() { return CacheWriter.disabledWriter(); } }, /** A writer that records interactions. */ MOCKITO { - @Override public CacheWriter get() { + @Override public CacheWriter create() { @SuppressWarnings("unchecked") - CacheWriter mock = Mockito.mock(CacheWriter.class); + CacheWriter mock = Mockito.mock(CacheWriter.class); return mock; } }, /** A writer that always throws an exception. */ EXCEPTIONAL { - @Override public CacheWriter get() { - return new RejectingCacheWriter(); + @Override public CacheWriter create() { + return new RejectingCacheWriter(); } }; + + public abstract CacheWriter create(); } /* ---------------- Executor -------------- */ @@ -545,26 +594,26 @@ enum ExecutorFailure { new ThreadFactoryBuilder().setDaemon(true).build()); /** The executors that the cache can be configured with. */ - enum CacheExecutor implements Supplier { + enum CacheExecutor { DEFAULT { // fork-join common pool - @Override public Executor get() { + @Override public Executor create() { // Use with caution as may be unpredictable during tests if awaiting completion return null; } }, DIRECT { - @Override public Executor get() { + @Override public Executor create() { // Cache implementations must avoid deadlocks by incorrectly assuming async execution return new TrackingExecutor(MoreExecutors.newDirectExecutorService()); } }, THREADED { - @Override public Executor get() { + @Override public Executor create() { return new TrackingExecutor(cachedExecutorService); } }, REJECTING { - @Override public Executor get() { + @Override public Executor create() { // Cache implementations must avoid corrupting internal state due to rejections return new ForkJoinPool() { @Override public void execute(Runnable task) { @@ -574,8 +623,7 @@ enum CacheExecutor implements Supplier { } }; - @Override - public abstract Executor get(); + public abstract Executor create(); } /* ---------------- Populated -------------- */ diff --git a/caffeine/src/test/java/com/github/benmanes/caffeine/cache/testing/CaffeineCacheFromContext.java b/caffeine/src/test/java/com/github/benmanes/caffeine/cache/testing/CaffeineCacheFromContext.java index 44ef795af1..42186b1601 100644 --- a/caffeine/src/test/java/com/github/benmanes/caffeine/cache/testing/CaffeineCacheFromContext.java +++ b/caffeine/src/test/java/com/github/benmanes/caffeine/cache/testing/CaffeineCacheFromContext.java @@ -23,6 +23,7 @@ import com.github.benmanes.caffeine.cache.RandomSeedEnforcer; import com.github.benmanes.caffeine.cache.Ticker; import com.github.benmanes.caffeine.cache.testing.CacheSpec.CacheExecutor; +import com.github.benmanes.caffeine.cache.testing.CacheSpec.CacheExpiry; import com.github.benmanes.caffeine.cache.testing.CacheSpec.CacheWeigher; import com.github.benmanes.caffeine.cache.testing.CacheSpec.Expire; import com.github.benmanes.caffeine.cache.testing.CacheSpec.InitialCapacity; @@ -58,6 +59,9 @@ public static Cache newCaffeineCache(CacheContext context) { builder.maximumWeight(context.maximumWeight()); } } + if (context.expiryType() != CacheExpiry.DISABLED) { + builder.expireAfter(context.expiry); + } if (context.afterAccess != Expire.DISABLED) { builder.expireAfterAccess(context.afterAccess.timeNanos(), TimeUnit.NANOSECONDS); } diff --git a/caffeine/src/test/java/com/github/benmanes/caffeine/cache/testing/ExpiryBuilder.java b/caffeine/src/test/java/com/github/benmanes/caffeine/cache/testing/ExpiryBuilder.java new file mode 100644 index 0000000000..7f7775fd0c --- /dev/null +++ b/caffeine/src/test/java/com/github/benmanes/caffeine/cache/testing/ExpiryBuilder.java @@ -0,0 +1,85 @@ +/* + * Copyright 2017 Ben Manes. All Rights Reserved. + * + * 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.github.benmanes.caffeine.cache.testing; + +import java.io.Serializable; + +import com.github.benmanes.caffeine.cache.Expiry; + +/** + * A builder for unit test convenience. + * + * @author ben.manes@gmail.com (Ben Manes) + */ +public final class ExpiryBuilder { + static final int UNSET = -1; + + private long createNanos = UNSET; + private long updateNanos = UNSET; + private long readNanos = UNSET; + + private ExpiryBuilder(long createNanos) { + this.createNanos = createNanos; + } + + /** Sets the fixed creation expiration time. */ + public static ExpiryBuilder expiringAfterCreate(long nanos) { + return new ExpiryBuilder(nanos); + } + + /** Sets the fixed update expiration time. */ + public ExpiryBuilder expiringAfterUpdate(long nanos) { + updateNanos = nanos; + return this; + } + + /** Sets the fixed read expiration time. */ + public ExpiryBuilder expiringAfterRead(long nanos) { + readNanos = nanos; + return this; + } + + public Expiry build() { + return new FixedExpiry(createNanos, updateNanos, readNanos); + } + + private static final class FixedExpiry implements Expiry, Serializable { + private static final long serialVersionUID = 1L; + + private final long createNanos; + private final long updateNanos; + private final long readNanos; + + FixedExpiry(long createNanos, long updateNanos, long readNanos) { + this.createNanos = createNanos; + this.updateNanos = updateNanos; + this.readNanos = readNanos; + } + + @Override + public long expireAfterCreate(K key, V value, long currentTime) { + return createNanos; + } + @Override + public long expireAfterUpdate(K key, V value, long currentTime, long currentDuration) { + return (updateNanos == UNSET) ? currentDuration : updateNanos; + } + @Override + public long expireAfterRead(K key, V value, long currentTime, long currentDuration) { + return (readNanos == UNSET) ? currentDuration : readNanos; + } + } +} diff --git a/gradle/dependencies.gradle b/gradle/dependencies.gradle index b4959148fc..24ded9ebf8 100644 --- a/gradle/dependencies.gradle +++ b/gradle/dependencies.gradle @@ -30,7 +30,7 @@ ext { commons_lang3: '3.5', config: '1.3.1', error_prone_annotations: '2.0.19', - fastutil: '7.1.0', + fastutil: '7.2.0', flip_tables: '1.0.2', guava: '21.0', javapoet: '1.8.0', @@ -43,13 +43,13 @@ ext { xz: '1.6', ] test_versions = [ - awaitility: '2.0.0', + awaitility: '3.0.0', easymock: '3.4', hamcrest: '2.0.0.0', jcache_tck: '1.0.1', jctools: '2.0.1', junit: '4.12', - mockito: '2.7.22', + mockito: '2.8.9', pax_exam: '4.10.0', testng: '6.11', truth: '0.24', @@ -58,9 +58,9 @@ ext { cache2k: '1.0.0.CR4', collision: '0.3.2', concurrentlinkedhashmap: '1.4.2', - ehcache2: '2.10.3', + ehcache2: '2.10.4', ehcache3: '3.3.1', - elastic_search: '5.3.0', + elastic_search: '5.3.2', expiring_map: '0.5.8', jackrabbit: '1.6.1', jamm: '0.3.1', @@ -74,7 +74,7 @@ ext { plugin_versions = [ buildscan: '1.6', buildscan_recipes: '0.2.0', - checkstyle: '7.6.1', + checkstyle: '7.7', coveralls: '2.8.1', coverity: '1.0.10', extra_conf: '3.1.0', diff --git a/gradle/jmh.gradle b/gradle/jmh.gradle index 8fee6cf493..c711eb244f 100644 --- a/gradle/jmh.gradle +++ b/gradle/jmh.gradle @@ -31,7 +31,7 @@ jmhJar { } jmh { - jmhVersion = '1.17.3' + jmhVersion = '1.18' if (project.hasProperty('includePattern')) { include = project.includePattern diff --git a/jcache/src/main/java/com/github/benmanes/caffeine/jcache/copy/JavaSerializationCopier.java b/jcache/src/main/java/com/github/benmanes/caffeine/jcache/copy/JavaSerializationCopier.java index 7f8b42a542..8a99d0fa89 100644 --- a/jcache/src/main/java/com/github/benmanes/caffeine/jcache/copy/JavaSerializationCopier.java +++ b/jcache/src/main/java/com/github/benmanes/caffeine/jcache/copy/JavaSerializationCopier.java @@ -57,7 +57,7 @@ protected byte[] serialize(Object object) { try (ObjectOutputStream output = new ObjectOutputStream(bytes)) { output.writeObject(object); } catch (IOException e) { - throw new UncheckedIOException("Failed to serialize " + e.getClass(), e); + throw new UncheckedIOException("Failed to serialize " + object.getClass(), e); } return bytes.toByteArray(); }