Skip to content

Commit

Permalink
Add java.time.Duration overloads to CacheBuilder:
Browse files Browse the repository at this point in the history
  cacheBuilder.expireAfterAccess(Duration)
  cacheBuilder.expireAfterWrite(Duration)
  cacheBuilder.refreshAfterWrite(Duration)

Fixes #2999

RELNOTES=Add `java.time.Duration` overloads to `CacheBuilder`.

-------------
Created by MOE: https://github.com/google/moe
MOE_MIGRATED_REVID=191116944
  • Loading branch information
kluever authored and ronshapiro committed Apr 4, 2018
1 parent a12ef6b commit 9bf6d95
Show file tree
Hide file tree
Showing 3 changed files with 182 additions and 7 deletions.
Expand Up @@ -23,6 +23,7 @@
import static com.google.common.cache.TestingRemovalListeners.queuingRemovalListener;
import static com.google.common.cache.TestingWeighers.constantWeigher;
import static com.google.common.truth.Truth.assertThat;
import static java.util.concurrent.TimeUnit.MILLISECONDS;
import static java.util.concurrent.TimeUnit.NANOSECONDS;
import static java.util.concurrent.TimeUnit.SECONDS;

Expand All @@ -41,7 +42,6 @@
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.Future;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicBoolean;
import java.util.concurrent.atomic.AtomicInteger;
import junit.framework.TestCase;
Expand Down Expand Up @@ -580,7 +580,7 @@ public String load(String key) throws InterruptedException {
CacheBuilder.newBuilder()
.recordStats()
.concurrencyLevel(2)
.expireAfterWrite(100, TimeUnit.MILLISECONDS)
.expireAfterWrite(100, MILLISECONDS)
.removalListener(removalListener)
.maximumSize(5000)
.build(countingIdentityLoader);
Expand All @@ -604,7 +604,7 @@ public void run() {
}

threadPool.shutdown();
threadPool.awaitTermination(300, TimeUnit.SECONDS);
threadPool.awaitTermination(300, SECONDS);

// Since we're not doing any more cache operations, and the cache only expires/evicts when doing
// other operations, the cache and the removal queue won't change from this point on.
Expand Down
93 changes: 90 additions & 3 deletions guava-tests/test/com/google/common/cache/CacheBuilderTest.java
Expand Up @@ -23,6 +23,7 @@
import static com.google.common.cache.TestingRemovalListeners.queuingRemovalListener;
import static com.google.common.cache.TestingWeighers.constantWeigher;
import static com.google.common.truth.Truth.assertThat;
import static java.util.concurrent.TimeUnit.MILLISECONDS;
import static java.util.concurrent.TimeUnit.NANOSECONDS;
import static java.util.concurrent.TimeUnit.SECONDS;

Expand All @@ -41,7 +42,6 @@
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.Future;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicBoolean;
import java.util.concurrent.atomic.AtomicInteger;
import junit.framework.TestCase;
Expand Down Expand Up @@ -268,6 +268,27 @@ public void testValueStrengthSetTwice() {
}
}

@GwtIncompatible // java.time.Duration
public void testLargeDurations() {
java.time.Duration threeHundredYears = java.time.Duration.ofDays(365 * 300);
CacheBuilder<Object, Object> builder = CacheBuilder.newBuilder();
try {
builder.expireAfterWrite(threeHundredYears);
fail();
} catch (ArithmeticException expected) {
}
try {
builder.expireAfterAccess(threeHundredYears);
fail();
} catch (ArithmeticException expected) {
}
try {
builder.refreshAfterWrite(threeHundredYears);
fail();
} catch (ArithmeticException expected) {
}
}

public void testTimeToLive_negative() {
CacheBuilder<Object, Object> builder = CacheBuilder.newBuilder();
try {
Expand All @@ -277,6 +298,16 @@ public void testTimeToLive_negative() {
}
}

@GwtIncompatible // java.time.Duration
public void testTimeToLive_negative_duration() {
CacheBuilder<Object, Object> builder = CacheBuilder.newBuilder();
try {
builder.expireAfterWrite(java.time.Duration.ofSeconds(-1));
fail();
} catch (IllegalArgumentException expected) {
}
}

public void testTimeToLive_small() {
CacheBuilder.newBuilder().expireAfterWrite(1, NANOSECONDS).build(identityLoader());
// well, it didn't blow up.
Expand All @@ -293,6 +324,18 @@ public void testTimeToLive_setTwice() {
}
}

@GwtIncompatible // java.time.Duration
public void testTimeToLive_setTwice_duration() {
CacheBuilder<Object, Object> builder =
CacheBuilder.newBuilder().expireAfterWrite(java.time.Duration.ofSeconds(3600));
try {
// even to the same value is not allowed
builder.expireAfterWrite(java.time.Duration.ofSeconds(3600));
fail();
} catch (IllegalStateException expected) {
}
}

public void testTimeToIdle_negative() {
CacheBuilder<Object, Object> builder = CacheBuilder.newBuilder();
try {
Expand All @@ -302,6 +345,16 @@ public void testTimeToIdle_negative() {
}
}

@GwtIncompatible // java.time.Duration
public void testTimeToIdle_negative_duration() {
CacheBuilder<Object, Object> builder = CacheBuilder.newBuilder();
try {
builder.expireAfterAccess(java.time.Duration.ofSeconds(-1));
fail();
} catch (IllegalArgumentException expected) {
}
}

public void testTimeToIdle_small() {
CacheBuilder.newBuilder().expireAfterAccess(1, NANOSECONDS).build(identityLoader());
// well, it didn't blow up.
Expand All @@ -318,6 +371,18 @@ public void testTimeToIdle_setTwice() {
}
}

@GwtIncompatible // java.time.Duration
public void testTimeToIdle_setTwice_duration() {
CacheBuilder<Object, Object> builder =
CacheBuilder.newBuilder().expireAfterAccess(java.time.Duration.ofSeconds(3600));
try {
// even to the same value is not allowed
builder.expireAfterAccess(java.time.Duration.ofSeconds(3600));
fail();
} catch (IllegalStateException expected) {
}
}

public void testTimeToIdleAndToLive() {
CacheBuilder.newBuilder()
.expireAfterWrite(1, NANOSECONDS)
Expand All @@ -336,6 +401,16 @@ public void testRefresh_zero() {
}
}

@GwtIncompatible // java.time.Duration
public void testRefresh_zero_duration() {
CacheBuilder<Object, Object> builder = CacheBuilder.newBuilder();
try {
builder.refreshAfterWrite(java.time.Duration.ZERO);
fail();
} catch (IllegalArgumentException expected) {
}
}

@GwtIncompatible // refreshAfterWrite
public void testRefresh_setTwice() {
CacheBuilder<Object, Object> builder =
Expand All @@ -348,6 +423,18 @@ public void testRefresh_setTwice() {
}
}

@GwtIncompatible // java.time.Duration
public void testRefresh_setTwice_duration() {
CacheBuilder<Object, Object> builder =
CacheBuilder.newBuilder().refreshAfterWrite(java.time.Duration.ofSeconds(3600));
try {
// even to the same value is not allowed
builder.refreshAfterWrite(java.time.Duration.ofSeconds(3600));
fail();
} catch (IllegalStateException expected) {
}
}

public void testTicker_setTwice() {
Ticker testTicker = Ticker.systemTicker();
CacheBuilder<Object, Object> builder = CacheBuilder.newBuilder().ticker(testTicker);
Expand Down Expand Up @@ -580,7 +667,7 @@ public String load(String key) throws InterruptedException {
CacheBuilder.newBuilder()
.recordStats()
.concurrencyLevel(2)
.expireAfterWrite(100, TimeUnit.MILLISECONDS)
.expireAfterWrite(100, MILLISECONDS)
.removalListener(removalListener)
.maximumSize(5000)
.build(countingIdentityLoader);
Expand All @@ -604,7 +691,7 @@ public void run() {
}

threadPool.shutdown();
threadPool.awaitTermination(300, TimeUnit.SECONDS);
threadPool.awaitTermination(300, SECONDS);

// Since we're not doing any more cache operations, and the cache only expires/evicts when doing
// other operations, the cache and the removal queue won't change from this point on.
Expand Down
90 changes: 89 additions & 1 deletion guava/src/com/google/common/cache/CacheBuilder.java
Expand Up @@ -30,6 +30,7 @@
import com.google.common.cache.AbstractCache.StatsCounter;
import com.google.common.cache.LocalCache.Strength;
import com.google.errorprone.annotations.CheckReturnValue;
import com.google.j2objc.annotations.J2ObjCIncompatible;
import java.lang.ref.SoftReference;
import java.lang.ref.WeakReference;
import java.util.ConcurrentModificationException;
Expand Down Expand Up @@ -65,7 +66,7 @@
* <pre>{@code
* LoadingCache<Key, Graph> graphs = CacheBuilder.newBuilder()
* .maximumSize(10000)
* .expireAfterWrite(10, TimeUnit.MINUTES)
* .expireAfterWrite(Duration.ofMinutes(10))
* .removalListener(MY_LISTENER)
* .build(
* new CacheLoader<Key, Graph>() {
Expand Down Expand Up @@ -621,6 +622,32 @@ Strength getValueStrength() {
return MoreObjects.firstNonNull(valueStrength, Strength.STRONG);
}

/**
* Specifies that each entry should be automatically removed from the cache once a fixed duration
* has elapsed after the entry's creation, or the most recent replacement of its value.
*
* <p>When {@code duration} is zero, this method hands off to {@link #maximumSize(long)
* maximumSize}{@code (0)}, ignoring any otherwise-specified maximum size or weight. This can be
* useful in testing, or to disable caching temporarily without a code change.
*
* <p>Expired entries may be counted in {@link Cache#size}, but will never be visible to read or
* write operations. Expired entries are cleaned up as part of the routine maintenance described
* in the class javadoc.
*
* @param duration the length of time after an entry is created that it should be automatically
* removed
* @return this {@code CacheBuilder} instance (for chaining)
* @throws IllegalArgumentException if {@code duration} is negative
* @throws IllegalStateException if the time to live or time to idle was already set
* @throws ArithmeticException for durations greater than +/- approximately 292 years
* @since NEXT
*/
@J2ObjCIncompatible
@GwtIncompatible // java.time.Duration
public CacheBuilder<K, V> expireAfterWrite(java.time.Duration duration) {
return expireAfterWrite(duration.toNanos(), TimeUnit.NANOSECONDS);
}

/**
* Specifies that each entry should be automatically removed from the cache once a fixed duration
* has elapsed after the entry's creation, or the most recent replacement of its value.
Expand Down Expand Up @@ -654,6 +681,35 @@ long getExpireAfterWriteNanos() {
return (expireAfterWriteNanos == UNSET_INT) ? DEFAULT_EXPIRATION_NANOS : expireAfterWriteNanos;
}

/**
* Specifies that each entry should be automatically removed from the cache once a fixed duration
* has elapsed after the entry's creation, the most recent replacement of its value, or its last
* access. Access time is reset by all cache read and write operations (including {@code
* Cache.asMap().get(Object)} and {@code Cache.asMap().put(K, V)}), but not by operations on the
* collection-views of {@link Cache#asMap}.
*
* <p>When {@code duration} is zero, this method hands off to {@link #maximumSize(long)
* maximumSize}{@code (0)}, ignoring any otherwise-specified maximum size or weight. This can be
* useful in testing, or to disable caching temporarily without a code change.
*
* <p>Expired entries may be counted in {@link Cache#size}, but will never be visible to read or
* write operations. Expired entries are cleaned up as part of the routine maintenance described
* in the class javadoc.
*
* @param duration the length of time after an entry is last accessed that it should be
* automatically removed
* @return this {@code CacheBuilder} instance (for chaining)
* @throws IllegalArgumentException if {@code duration} is negative
* @throws IllegalStateException if the time to idle or time to live was already set
* @throws ArithmeticException for durations greater than +/- approximately 292 years
* @since NEXT
*/
@J2ObjCIncompatible
@GwtIncompatible // java.time.Duration
public CacheBuilder<K, V> expireAfterAccess(java.time.Duration duration) {
return expireAfterAccess(duration.toNanos(), TimeUnit.NANOSECONDS);
}

/**
* Specifies that each entry should be automatically removed from the cache once a fixed duration
* has elapsed after the entry's creation, the most recent replacement of its value, or its last
Expand Down Expand Up @@ -692,6 +748,38 @@ long getExpireAfterAccessNanos() {
: expireAfterAccessNanos;
}

/**
* Specifies that active entries are eligible for automatic refresh once a fixed duration has
* elapsed after the entry's creation, or the most recent replacement of its value. The semantics
* of refreshes are specified in {@link LoadingCache#refresh}, and are performed by calling {@link
* CacheLoader#reload}.
*
* <p>As the default implementation of {@link CacheLoader#reload} is synchronous, it is
* recommended that users of this method override {@link CacheLoader#reload} with an asynchronous
* implementation; otherwise refreshes will be performed during unrelated cache read and write
* operations.
*
* <p>Currently automatic refreshes are performed when the first stale request for an entry
* occurs. The request triggering refresh will make a blocking call to {@link CacheLoader#reload}
* and immediately return the new value if the returned future is complete, and the old value
* otherwise.
*
* <p><b>Note:</b> <i>all exceptions thrown during refresh will be logged and then swallowed</i>.
*
* @param duration the length of time after an entry is created that it should be considered
* stale, and thus eligible for refresh
* @return this {@code CacheBuilder} instance (for chaining)
* @throws IllegalArgumentException if {@code duration} is negative
* @throws IllegalStateException if the refresh interval was already set
* @throws ArithmeticException for durations greater than +/- approximately 292 years
* @since NEXT
*/
@J2ObjCIncompatible
@GwtIncompatible // java.time.Duration
public CacheBuilder<K, V> refreshAfterWrite(java.time.Duration duration) {
return refreshAfterWrite(duration.toNanos(), TimeUnit.NANOSECONDS);
}

/**
* Specifies that active entries are eligible for automatic refresh once a fixed duration has
* elapsed after the entry's creation, or the most recent replacement of its value. The semantics
Expand Down

0 comments on commit 9bf6d95

Please sign in to comment.