-
Notifications
You must be signed in to change notification settings - Fork 1.6k
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Fix variable expiration overflowing with the maximum duration (fixes #…
…217) When the duration is set to the maximum length, Long.MAX_VALUE nanoseconds, the calcuation of expirationTime - currentTime > 0 may overflow and be negative. This will not occur if the same thread calculates both timestamps. It may occur across threads when the expirationTime is concurrently updated using a later base time than t1's reading of the currentTime. This can occur whenever the maintenance work is triggered to sweep expired entries and a user thread accesses the entry. The later timestamp plus the maximum duration results in an overflow, causing the remaining time to be negative, and therefore causes the cache to expire the entry. The internal maximum is now capped at Long.MAX_VALUE / 2 or ~150 years. This should give a broad safety net to avoid these concurrency-inducing overflows in normal code.
- Loading branch information
Showing
5 changed files
with
50 additions
and
34 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -15,6 +15,7 @@ | |
*/ | ||
package com.github.benmanes.caffeine.cache; | ||
|
||
import static com.github.benmanes.caffeine.cache.BoundedLocalCache.MAXIMUM_EXPIRY; | ||
import static java.util.Objects.requireNonNull; | ||
|
||
import java.io.Serializable; | ||
|
@@ -31,7 +32,6 @@ | |
* @author [email protected] (Ben Manes) | ||
*/ | ||
final class Async { | ||
static final long MAXIMUM_EXPIRY = (Long.MAX_VALUE >> 1); // 150 years | ||
|
||
private Async() {} | ||
|
||
|
@@ -120,8 +120,7 @@ Object writeReplace() { | |
* 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. | ||
* expiration is updated and the expiration timeouts reflect the value once present. | ||
*/ | ||
static final class AsyncExpiry<K, V> implements Expiry<K, CompletableFuture<V>>, Serializable { | ||
private static final long serialVersionUID = 1L; | ||
|
@@ -134,33 +133,28 @@ static final class AsyncExpiry<K, V> implements Expiry<K, CompletableFuture<V>>, | |
|
||
@Override | ||
public long expireAfterCreate(K key, CompletableFuture<V> future, long currentTime) { | ||
if (isReady(future)) { | ||
long duration = delegate.expireAfterCreate(key, future.join(), currentTime); | ||
return Math.min(duration, MAXIMUM_EXPIRY); | ||
} | ||
return Long.MAX_VALUE; | ||
return isReady(future) | ||
? delegate.expireAfterCreate(key, future.join(), currentTime) | ||
: Long.MAX_VALUE; | ||
} | ||
|
||
@Override | ||
public long expireAfterUpdate(K key, CompletableFuture<V> future, | ||
long currentTime, long currentDuration) { | ||
if (isReady(future)) { | ||
long duration = (currentDuration > MAXIMUM_EXPIRY) | ||
return (currentDuration > MAXIMUM_EXPIRY) | ||
? delegate.expireAfterCreate(key, future.join(), currentTime) | ||
: delegate.expireAfterUpdate(key, future.join(), currentTime, currentDuration); | ||
return Math.min(duration, MAXIMUM_EXPIRY); | ||
} | ||
return Long.MAX_VALUE; | ||
} | ||
|
||
@Override | ||
public long expireAfterRead(K key, CompletableFuture<V> future, | ||
long currentTime, long currentDuration) { | ||
if (isReady(future)) { | ||
long duration = delegate.expireAfterRead(key, future.join(), currentTime, currentDuration); | ||
return Math.min(duration, MAXIMUM_EXPIRY); | ||
} | ||
return Long.MAX_VALUE; | ||
return isReady(future) | ||
? delegate.expireAfterRead(key, future.join(), currentTime, currentDuration) | ||
: Long.MAX_VALUE; | ||
} | ||
|
||
Object writeReplace() { | ||
|
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters