asyncLoad(@Nonnull K key, @Nonnull Executor executor);
+
+ /**
+ * Asynchronously computes or retrieves the values corresponding to {@code keys}. This method is
+ * called by {@link AsyncLoadingCache#getAll}.
+ *
+ * If the returned map doesn't contain all requested {@code keys} then the entries it does contain
+ * will be cached and {@code getAll} will return the partial results. If the returned map contains
+ * extra keys not present in {@code keys} then all returned entries will be cached, but only the
+ * entries for {@code keys} will be returned from {@code getAll}.
+ *
+ * This method should be overridden when bulk retrieval is significantly more efficient than many
+ * individual lookups. Note that {@link AsyncLoadingCache#getAll} will defer to individual calls
+ * to {@link AsyncLoadingCache#get} if this method is not overridden.
+ *
+ * @param keys the unique, non-null keys whose values should be loaded
+ * @param executor the executor with which the entries are asynchronously loaded
+ * @return a future containing the map from each key in {@code keys} to the value associated with
+ * that key; may not contain null values
+ */
+ @Nonnull
+ default CompletableFuture> asyncLoadAll(
+ @Nonnull Iterable extends K> keys, @Nonnull Executor executor) {
+ throw new UnsupportedOperationException();
+ }
+
+ /**
+ * Asynchronously computes or retrieves a replacement value corresponding to an already-cached
+ * {@code key}. If the replacement value is not found then the mapping will be removed if
+ * {@code null} is computed. This method is called when an existing cache entry is refreshed by
+ * {@link Caffeine#refreshAfterWrite}, or through a call to {@link LoadingCache#refresh}.
+ *
+ * Note: all exceptions thrown by this method will be logged and then swallowed .
+ *
+ * @param key the non-null key whose value should be loaded
+ * @param oldValue the non-null old value corresponding to {@code key}
+ * @param executor the executor with which the entry is asynchronously loaded
+ * @return a future containing the new value associated with {@code key}, or containing
+ * {@code null} if the mapping is to be removed
+ */
+ @Nonnull
+ default CompletableFuture asyncReload(
+ @Nonnull K key, @Nonnull V oldValue, @Nonnull Executor executor) {
+ return asyncLoad(key, executor);
+ }
+}
diff --git a/caffeine/src/main/java/com/github/benmanes/caffeine/cache/AsyncLoadingCache.java b/caffeine/src/main/java/com/github/benmanes/caffeine/cache/AsyncLoadingCache.java
index bc3b478c9f..747e0aea97 100644
--- a/caffeine/src/main/java/com/github/benmanes/caffeine/cache/AsyncLoadingCache.java
+++ b/caffeine/src/main/java/com/github/benmanes/caffeine/cache/AsyncLoadingCache.java
@@ -93,7 +93,7 @@ CompletableFuture get(@Nonnull K key,
* @throws RuntimeException or Error if the mappingFunction does when constructing the future,
* in which case the mapping is left unestablished
*/
- @CheckForNull
+ @Nonnull
CompletableFuture get(@Nonnull K key,
@Nonnull BiFunction super K, Executor, CompletableFuture> mappingFunction);
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 86cfb30390..33dc8f045f 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
@@ -2675,14 +2675,14 @@ static final class BoundedLocalAsyncLoadingCache
Policy policy;
@SuppressWarnings("unchecked")
- BoundedLocalAsyncLoadingCache(Caffeine builder, CacheLoader super K, V> loader) {
+ BoundedLocalAsyncLoadingCache(Caffeine builder, AsyncCacheLoader super K, V> loader) {
super(LocalCacheFactory.newBoundedLocalCache((Caffeine>) builder,
asyncLoader(loader, builder), true), loader);
isWeighted = builder.isWeighted();
}
private static CacheLoader super K, CompletableFuture> asyncLoader(
- CacheLoader super K, V> loader, Caffeine, ?> builder) {
+ AsyncCacheLoader super K, V> loader, Caffeine, ?> builder) {
Executor executor = builder.getExecutor();
return key -> loader.asyncLoad(key, executor);
}
diff --git a/caffeine/src/main/java/com/github/benmanes/caffeine/cache/CacheLoader.java b/caffeine/src/main/java/com/github/benmanes/caffeine/cache/CacheLoader.java
index 80a2808f5d..ecd033ba03 100644
--- a/caffeine/src/main/java/com/github/benmanes/caffeine/cache/CacheLoader.java
+++ b/caffeine/src/main/java/com/github/benmanes/caffeine/cache/CacheLoader.java
@@ -44,7 +44,7 @@
@ThreadSafe
@FunctionalInterface
@SuppressWarnings("PMD.SignatureDeclareThrowsException")
-public interface CacheLoader {
+public interface CacheLoader extends AsyncCacheLoader {
/**
* Computes or retrieves the value corresponding to {@code key}.
@@ -96,7 +96,7 @@ default Map loadAll(@Nonnull Iterable extends K> keys) throws Exception
* @param executor the executor that asynchronously loads the entry
* @return the future value associated with {@code key}
*/
- @Nonnull
+ @Override @Nonnull
default CompletableFuture asyncLoad(@Nonnull K key, @Nonnull Executor executor) {
requireNonNull(key);
requireNonNull(executor);
@@ -129,9 +129,9 @@ default CompletableFuture asyncLoad(@Nonnull K key, @Nonnull Executor executo
* @return a future containing the map from each key in {@code keys} to the value associated with
* that key; may not contain null values
*/
- @Nonnull
+ @Override @Nonnull
default CompletableFuture> asyncLoadAll(
- Iterable extends K> keys, @Nonnull Executor executor) {
+ @Nonnull Iterable extends K> keys, @Nonnull Executor executor) {
requireNonNull(keys);
requireNonNull(executor);
return CompletableFuture.supplyAsync(() -> {
@@ -166,4 +166,34 @@ default CompletableFuture> asyncLoadAll(
default V reload(@Nonnull K key, @Nonnull V oldValue) throws Exception {
return load(key);
}
+
+ /**
+ * Asynchronously computes or retrieves a replacement value corresponding to an already-cached
+ * {@code key}. If the replacement value is not found then the mapping will be removed if
+ * {@code null} is computed. This method is called when an existing cache entry is refreshed by
+ * {@link Caffeine#refreshAfterWrite}, or through a call to {@link LoadingCache#refresh}.
+ *
+ * Note: all exceptions thrown by this method will be logged and then swallowed .
+ *
+ * @param key the non-null key whose value should be loaded
+ * @param oldValue the non-null old value corresponding to {@code key}
+ * @param executor the executor with which the entry is asynchronously loaded
+ * @return a future containing the new value associated with {@code key}, or containing
+ * {@code null} if the mapping is to be removed
+ */
+ @Override @Nonnull
+ default CompletableFuture asyncReload(
+ @Nonnull K key, @Nonnull V oldValue, @Nonnull Executor executor) {
+ requireNonNull(key);
+ requireNonNull(executor);
+ return CompletableFuture.supplyAsync(() -> {
+ try {
+ return reload(key, oldValue);
+ } catch (RuntimeException e) {
+ throw e;
+ } catch (Exception e) {
+ throw new CompletionException(e);
+ }
+ }, executor);
+ }
}
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 5efcaa6701..ee2c83d3bb 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
@@ -833,9 +833,9 @@ public LoadingCache build(
/**
* Builds a cache, which either returns a {@link CompletableFuture} already loaded or currently
* computing the value for a given key or atomically computes the value asynchronously through a
- * supplied mapping function or the supplied {@code CacheLoader}. If the asynchronous
- * computation fails then the entry will be automatically removed. Note that multiple threads can
- * concurrently load values for distinct keys.
+ * supplied mapping function or the supplied {@code CacheLoader}. If the asynchronous computation
+ * fails or computes a {@code null} value then the entry will be automatically removed. Note that
+ * multiple threads can concurrently load values for distinct keys.
*
* This method does not alter the state of this {@code Caffeine} instance, so it can be invoked
* again to create multiple independent caches.
@@ -850,6 +850,29 @@ public LoadingCache build(
@Nonnull
public AsyncLoadingCache buildAsync(
@Nonnull CacheLoader super K1, V1> loader) {
+ return buildAsync((AsyncCacheLoader super K1, V1>) loader);
+ }
+
+ /**
+ * Builds a cache, which either returns a {@link CompletableFuture} already loaded or currently
+ * computing the value for a given key or atomically computes the value asynchronously through a
+ * supplied mapping function or the supplied {@code AsyncCacheLoader}. If the asynchronous
+ * computation fails or computes a {@code null} value then the entry will be automatically
+ * removed. Note that multiple threads can concurrently load values for distinct keys.
+ *
+ * This method does not alter the state of this {@code Caffeine} instance, so it can be invoked
+ * again to create multiple independent caches.
+ *
+ * @param loader the cache loader used to obtain new values
+ * @param the key type of the loader
+ * @param the value type of the loader
+ * @return a cache having the requested features
+ * @throws IllegalStateException if the value strength is weak or soft
+ * @throws NullPointerException if the specified cache loader is null
+ */
+ @Nonnull
+ public AsyncLoadingCache buildAsync(
+ @Nonnull AsyncCacheLoader super K1, V1> loader) {
requireState(valueStrength == null);
requireState(writer == null);
requireWeightWithWeigher();
diff --git a/caffeine/src/main/java/com/github/benmanes/caffeine/cache/LocalAsyncLoadingCache.java b/caffeine/src/main/java/com/github/benmanes/caffeine/cache/LocalAsyncLoadingCache.java
index 8beb4fde14..9fc20ddb66 100644
--- a/caffeine/src/main/java/com/github/benmanes/caffeine/cache/LocalAsyncLoadingCache.java
+++ b/caffeine/src/main/java/com/github/benmanes/caffeine/cache/LocalAsyncLoadingCache.java
@@ -59,12 +59,13 @@ abstract class LocalAsyncLoadingCache loader;
+ final AsyncCacheLoader loader;
+
LoadingCacheView localCacheView;
@SuppressWarnings("unchecked")
- LocalAsyncLoadingCache(C cache, CacheLoader super K, V> loader) {
- this.loader = (CacheLoader) loader;
+ LocalAsyncLoadingCache(C cache, AsyncCacheLoader super K, V> loader) {
+ this.loader = (AsyncCacheLoader) loader;
this.canBulkLoad = canBulkLoad(loader);
this.cache = cache;
}
@@ -73,13 +74,24 @@ abstract class LocalAsyncLoadingCache policy();
/** Returns whether the supplied cache loader has bulk load functionality. */
- private static boolean canBulkLoad(CacheLoader, ?> loader) {
+ private static boolean canBulkLoad(AsyncCacheLoader, ?> loader) {
try {
- Method loadAll = loader.getClass().getMethod(
- "loadAll", Iterable.class);
- Method asyncLoadAll = loader.getClass().getMethod(
+ Class> defaultLoaderClass = AsyncCacheLoader.class;
+ if (loader instanceof CacheLoader, ?>) {
+ defaultLoaderClass = CacheLoader.class;
+
+ Method classLoadAll = loader.getClass().getMethod("loadAll", Iterable.class);
+ Method defaultLoadAll = CacheLoader.class.getMethod("loadAll", Iterable.class);
+ if (!classLoadAll.equals(defaultLoadAll)) {
+ return true;
+ }
+ }
+
+ Method classAsyncLoadAll = loader.getClass().getMethod(
"asyncLoadAll", Iterable.class, Executor.class);
- return !loadAll.isDefault() || !asyncLoadAll.isDefault();
+ Method defaultAsyncLoadAll = defaultLoaderClass.getMethod(
+ "asyncLoadAll", Iterable.class, Executor.class);
+ return !classAsyncLoadAll.equals(defaultAsyncLoadAll);
} catch (NoSuchMethodException | SecurityException e) {
logger.log(Level.WARNING, "Cannot determine if CacheLoader can bulk load", e);
return false;
@@ -436,12 +448,26 @@ public void refresh(K key) {
BiFunction, CompletableFuture> refreshFunction =
(k, oldValueFuture) -> {
try {
- V oldValue = (oldValueFuture == null) ? null : oldValueFuture.join();
- V newValue = (oldValue == null) ? loader.load(key) : loader.reload(key, oldValue);
- return (newValue == null) ? null : CompletableFuture.completedFuture(newValue);
+ V oldValue = Async.getWhenSuccessful(oldValueFuture);
+ if (loader instanceof CacheLoader, ?>) {
+ CacheLoader cacheLoader = (CacheLoader) loader;
+ V newValue = (oldValue == null)
+ ? cacheLoader.load(key)
+ : cacheLoader.reload(key, oldValue);
+ return (newValue == null) ? null : CompletableFuture.completedFuture(newValue);
+ } else {
+ // Hint that the async task should be run on this async task's thread
+ CompletableFuture newValueFuture = (oldValue == null)
+ ? loader.asyncLoad(key, Runnable::run)
+ : loader.asyncReload(key, oldValue, Runnable::run);
+ V newValue = newValueFuture.get();
+ return (newValue == null) ? null : newValueFuture;
+ }
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
return LocalCache.throwUnchecked(e);
+ } catch (ExecutionException e) {
+ return LocalCache.throwUnchecked(e.getCause());
} catch (Exception e) {
return LocalCache.throwUnchecked(e);
}
diff --git a/caffeine/src/main/java/com/github/benmanes/caffeine/cache/LocalCache.java b/caffeine/src/main/java/com/github/benmanes/caffeine/cache/LocalCache.java
index 8112a89cab..1a6c1a1668 100644
--- a/caffeine/src/main/java/com/github/benmanes/caffeine/cache/LocalCache.java
+++ b/caffeine/src/main/java/com/github/benmanes/caffeine/cache/LocalCache.java
@@ -179,7 +179,7 @@ default void invalidateAll(Iterable> keys) {
}
@SuppressWarnings({"unchecked", "TypeParameterUnusedInFormals"})
- static V throwUnchecked(Exception e) throws T {
- throw (T) e;
+ static V throwUnchecked(Throwable t) throws T {
+ throw (T) t;
}
}
diff --git a/caffeine/src/main/java/com/github/benmanes/caffeine/cache/LocalLoadingCache.java b/caffeine/src/main/java/com/github/benmanes/caffeine/cache/LocalLoadingCache.java
index 3bc134197a..5164455d48 100644
--- a/caffeine/src/main/java/com/github/benmanes/caffeine/cache/LocalLoadingCache.java
+++ b/caffeine/src/main/java/com/github/benmanes/caffeine/cache/LocalLoadingCache.java
@@ -17,6 +17,7 @@
import static java.util.Objects.requireNonNull;
+import java.lang.reflect.Method;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
@@ -52,7 +53,9 @@ interface LocalLoadingCache, K, V>
/** Returns whether the supplied cache loader has bulk load functionality. */
default boolean hasLoadAll(CacheLoader super K, V> loader) {
try {
- return !loader.getClass().getMethod("loadAll", Iterable.class).isDefault();
+ Method classLoadAll = loader.getClass().getMethod("loadAll", Iterable.class);
+ Method defaultLoadAll = CacheLoader.class.getMethod("loadAll", Iterable.class);
+ return !classLoadAll.equals(defaultLoadAll);
} catch (NoSuchMethodException | SecurityException e) {
logger.log(Level.WARNING, "Cannot determine if CacheLoader can bulk load", e);
return false;
diff --git a/caffeine/src/main/java/com/github/benmanes/caffeine/cache/SerializationProxy.java b/caffeine/src/main/java/com/github/benmanes/caffeine/cache/SerializationProxy.java
index 03204b2a8b..66b62f1aa9 100644
--- a/caffeine/src/main/java/com/github/benmanes/caffeine/cache/SerializationProxy.java
+++ b/caffeine/src/main/java/com/github/benmanes/caffeine/cache/SerializationProxy.java
@@ -34,12 +34,12 @@ final class SerializationProxy implements Serializable {
boolean weakValues;
boolean softValues;
Weigher, ?> weigher;
- CacheLoader, ?> loader;
CacheWriter, ?> writer;
boolean isRecordingStats;
long expiresAfterWriteNanos;
long expiresAfterAccessNanos;
long refreshAfterWriteNanos;
+ AsyncCacheLoader, ?> loader;
RemovalListener, ?> removalListener;
long maximumSize = Caffeine.UNSET_INT;
long maximumWeight = Caffeine.UNSET_INT;
@@ -92,7 +92,9 @@ Object readResolve() {
if (async) {
return builder.buildAsync(loader);
} else if (loader != null) {
- return builder.build(loader);
+ @SuppressWarnings("unchecked")
+ CacheLoader cacheLoader = (CacheLoader) loader;
+ return builder.build(cacheLoader);
}
return builder.build();
}
diff --git a/caffeine/src/main/java/com/github/benmanes/caffeine/cache/UnboundedLocalCache.java b/caffeine/src/main/java/com/github/benmanes/caffeine/cache/UnboundedLocalCache.java
index 34cf780bb0..dce03d7819 100644
--- a/caffeine/src/main/java/com/github/benmanes/caffeine/cache/UnboundedLocalCache.java
+++ b/caffeine/src/main/java/com/github/benmanes/caffeine/cache/UnboundedLocalCache.java
@@ -889,7 +889,7 @@ static final class UnboundedLocalAsyncLoadingCache
Policy policy;
@SuppressWarnings("unchecked")
- UnboundedLocalAsyncLoadingCache(Caffeine builder, CacheLoader super K, V> loader) {
+ UnboundedLocalAsyncLoadingCache(Caffeine builder, AsyncCacheLoader super K, V> loader) {
super(new UnboundedLocalCache<>((Caffeine>) builder, true), loader);
}
diff --git a/caffeine/src/test/java/com/github/benmanes/caffeine/cache/CaffeineTest.java b/caffeine/src/test/java/com/github/benmanes/caffeine/cache/CaffeineTest.java
index cb575fdda1..6edae6fd5d 100644
--- a/caffeine/src/test/java/com/github/benmanes/caffeine/cache/CaffeineTest.java
+++ b/caffeine/src/test/java/com/github/benmanes/caffeine/cache/CaffeineTest.java
@@ -115,9 +115,22 @@ public void loading_nullLoader() {
/* ---------------- async -------------- */
- @Test(expectedExceptions = NullPointerException.class)
+ @Test
public void async_nullLoader() {
- Caffeine.newBuilder().buildAsync(null);
+ try {
+ Caffeine.newBuilder().buildAsync((CacheLoader, ?>) null);
+ Assert.fail();
+ } catch (NullPointerException expected) {}
+
+ try {
+ Caffeine.newBuilder().buildAsync((AsyncCacheLoader, ?>) null);
+ Assert.fail();
+ } catch (NullPointerException expected) {}
+ }
+
+ @Test
+ public void async_asyncLoader() {
+ Caffeine.newBuilder().buildAsync(loader::asyncLoad);
}
@Test(expectedExceptions = IllegalStateException.class)
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 078bcdfb74..b6122d844d 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
@@ -59,18 +59,22 @@
* @author ben.manes@gmail.com (Ben Manes)
*/
public final class CacheContext {
+ final RemovalListener removalListener;
+ final CacheWriter cacheWriter;
final InitialCapacity initialCapacity;
+ final Map original;
final Implementation implementation;
final Listener removalListenerType;
final CacheExecutor cacheExecutor;
final ReferenceType valueStrength;
final ReferenceType keyStrength;
final TrackingExecutor executor;
- final Maximum maximumSize;
final Population population;
final CacheWeigher weigher;
+ final Maximum maximumSize;
final Expire afterAccess;
final Expire afterWrite;
+ final FakeTicker ticker;
final Compute compute;
final Advance advance;
final Expire refresh;
@@ -78,10 +82,7 @@ public final class CacheContext {
final Writer writer;
final Stats stats;
- final FakeTicker ticker;
- final Map original;
- final CacheWriter cacheWriter;
- final RemovalListener removalListener;
+ final boolean isAsyncLoading;
Cache, ?> cache;
AsyncLoadingCache, ?> asyncCache;
@@ -101,7 +102,7 @@ public CacheContext(InitialCapacity initialCapacity, Stats stats, CacheWeigher w
Maximum maximumSize, Expire afterAccess, Expire afterWrite, Expire refresh,
Advance advance, ReferenceType keyStrength, ReferenceType valueStrength,
CacheExecutor cacheExecutor, Listener removalListenerType, Population population,
- boolean isLoading, Compute compute, Loader loader, Writer writer,
+ boolean isLoading, boolean isAsyncLoading, Compute compute, Loader loader, Writer writer,
Implementation implementation) {
this.initialCapacity = requireNonNull(initialCapacity);
this.stats = requireNonNull(stats);
@@ -119,6 +120,7 @@ public CacheContext(InitialCapacity initialCapacity, Stats stats, CacheWeigher w
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.ticker = new FakeTicker();
@@ -287,6 +289,10 @@ public boolean isLoading() {
return (loader != null);
}
+ public boolean isAsyncLoading() {
+ return isAsyncLoading;
+ }
+
public Loader loader() {
return loader;
}
@@ -382,6 +388,7 @@ public String toString() {
.add("valueStrength", valueStrength)
.add("compute", compute)
.add("loader", loader)
+ .add("isAsyncLoading", isAsyncLoading)
.add("writer", writer)
.add("cacheExecutor", cacheExecutor)
.add("removalListener", removalListenerType)
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 23280a7673..9514e1fd52 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
@@ -78,6 +78,7 @@ public Stream>> generate() {
/** Returns the Cartesian set of the possible cache configurations. */
@SuppressWarnings("unchecked")
private Set> combinations() {
+ Set asyncLoading = ImmutableSet.of(true, false);
Set statistics = filterTypes(options.stats(), cacheSpec.stats());
Set keys = filterTypes(options.keys(), cacheSpec.keys());
Set values = filterTypes(options.values(), cacheSpec.values());
@@ -96,6 +97,9 @@ private Set> combinations() {
? ImmutableSet.of(Implementation.Caffeine)
: ImmutableSet.of();
}
+ if (computations.equals(ImmutableSet.of(Compute.SYNC))) {
+ asyncLoading = ImmutableSet.of(false);
+ }
if (computations.isEmpty() || implementations.isEmpty() || keys.isEmpty() || values.isEmpty()) {
return ImmutableSet.of();
@@ -115,6 +119,7 @@ private Set> combinations() {
ImmutableSet.copyOf(cacheSpec.removalListener()),
ImmutableSet.copyOf(cacheSpec.population()),
ImmutableSet.of(true, isLoadingOnly),
+ ImmutableSet.copyOf(asyncLoading),
ImmutableSet.copyOf(computations),
ImmutableSet.copyOf(cacheSpec.loader()),
ImmutableSet.copyOf(cacheSpec.writer()),
@@ -149,6 +154,7 @@ private CacheContext newCacheContext(List combination) {
(Listener) combination.get(index++),
(Population) combination.get(index++),
(Boolean) combination.get(index++),
+ (Boolean) combination.get(index++),
(Compute) combination.get(index++),
(Loader) combination.get(index++),
(Writer) combination.get(index++),
@@ -159,14 +165,16 @@ private CacheContext newCacheContext(List combination) {
private boolean isCompatible(CacheContext context) {
boolean asyncIncompatible = context.isAsync()
&& ((context.implementation() != Implementation.Caffeine)
- || !context.isStrongValues() || !context.isLoading());
+ || !context.isStrongValues() || !context.isLoading());
+ boolean asyncLoaderIncompatible = context.isAsyncLoading()
+ && (!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 skip = asyncIncompatible
+ boolean skip = asyncIncompatible || asyncLoaderIncompatible
|| refreshIncompatible || weigherIncompatible
|| 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 4f9ce7e7e8..6e9dcd8beb 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
@@ -18,6 +18,8 @@
import static java.lang.annotation.ElementType.METHOD;
import static java.lang.annotation.RetentionPolicy.RUNTIME;
+import java.io.ObjectStreamException;
+import java.io.Serializable;
import java.lang.annotation.Retention;
import java.lang.annotation.Target;
import java.util.ArrayList;
@@ -25,6 +27,8 @@
import java.util.HashMap;
import java.util.List;
import java.util.Map;
+import java.util.concurrent.CompletableFuture;
+import java.util.concurrent.Executor;
import java.util.concurrent.Executors;
import java.util.concurrent.ThreadLocalRandom;
import java.util.concurrent.TimeUnit;
@@ -32,6 +36,7 @@
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.LoadingCache;
@@ -354,32 +359,32 @@ Loader[] loader() default {
/** The {@link CacheLoader} for constructing the {@link LoadingCache}. */
enum Loader implements CacheLoader {
/** A loader that always returns null (no mapping). */
- NULL(false) {
+ NULL {
@Override public Integer load(Integer key) {
return null;
}
},
/** A loader that returns the key. */
- IDENTITY(false) {
+ IDENTITY {
@Override public Integer load(Integer key) {
return key;
}
},
/** A loader that returns the key's negation. */
- NEGATIVE(false) {
+ NEGATIVE {
@Override public Integer load(Integer key) {
return interner.get().intern(-key);
}
},
/** A loader that always throws an exception. */
- EXCEPTIONAL(false) {
+ EXCEPTIONAL {
@Override public Integer load(Integer key) {
throw new IllegalStateException();
}
},
/** A loader that always returns null (no mapping). */
- BULK_NULL(true) {
+ BULK_NULL {
@Override public Integer load(Integer key) {
throw new UnsupportedOperationException();
}
@@ -387,7 +392,7 @@ enum Loader implements CacheLoader {
return null;
}
},
- BULK_IDENTITY(true) {
+ BULK_IDENTITY {
@Override public Integer load(Integer key) {
throw new UnsupportedOperationException();
}
@@ -397,7 +402,7 @@ enum Loader implements CacheLoader {
return result;
}
},
- BULK_NEGATIVE(true) {
+ BULK_NEGATIVE {
@Override public Integer load(Integer key) {
throw new UnsupportedOperationException();
}
@@ -408,7 +413,7 @@ enum Loader implements CacheLoader {
}
},
/** A bulk-only loader that loads more than requested. */
- BULK_NEGATIVE_EXCEEDS(true) {
+ BULK_NEGATIVE_EXCEEDS {
@Override public Integer load(Integer key) {
throw new UnsupportedOperationException();
}
@@ -422,7 +427,7 @@ enum Loader implements CacheLoader {
}
},
/** A bulk-only loader that always throws an exception. */
- BULK_EXCEPTIONAL(true) {
+ BULK_EXCEPTIONAL {
@Override public Integer load(Integer key) {
throw new UnsupportedOperationException();
}
@@ -432,14 +437,55 @@ enum Loader implements CacheLoader {
};
private final boolean bulk;
+ private final AsyncCacheLoader asyncLoader;
- private Loader(boolean bulk) {
- this.bulk = bulk;
+ private Loader() {
+ bulk = name().startsWith("BULK");
+ asyncLoader = bulk
+ ? new BulkSeriazableAsyncCacheLoader(this)
+ : new SeriazableAsyncCacheLoader(this);
}
public boolean isBulk() {
return bulk;
}
+
+ /** Returns a serializable view restricted to the {@link AsyncCacheLoader} interface. */
+ public AsyncCacheLoader async() {
+ return asyncLoader;
+ }
+
+ private static class SeriazableAsyncCacheLoader
+ implements AsyncCacheLoader, Serializable {
+ private static final long serialVersionUID = 1L;
+
+ final Loader loader;
+
+ SeriazableAsyncCacheLoader(Loader loader) {
+ this.loader = loader;
+ }
+ @Override public CompletableFuture asyncLoad(Integer key, Executor executor) {
+ return loader.asyncLoad(key, executor);
+ }
+ private Object readResolve() throws ObjectStreamException {
+ return loader.asyncLoader;
+ }
+ }
+
+ private static final class BulkSeriazableAsyncCacheLoader extends SeriazableAsyncCacheLoader {
+ private static final long serialVersionUID = 1L;
+
+ BulkSeriazableAsyncCacheLoader(Loader loader) {
+ super(loader);
+ }
+ @Override public CompletableFuture asyncLoad(Integer key, Executor executor) {
+ throw new IllegalStateException();
+ }
+ @Override public CompletableFuture> asyncLoadAll(
+ Iterable extends Integer> keys, Executor executor) {
+ return loader.asyncLoadAll(keys, executor);
+ }
+ }
}
/* ---------------- CacheWriter -------------- */
diff --git a/caffeine/src/test/java/com/github/benmanes/caffeine/cache/testing/CacheValidationListener.java b/caffeine/src/test/java/com/github/benmanes/caffeine/cache/testing/CacheValidationListener.java
index 36f9538855..28ef41e788 100644
--- a/caffeine/src/test/java/com/github/benmanes/caffeine/cache/testing/CacheValidationListener.java
+++ b/caffeine/src/test/java/com/github/benmanes/caffeine/cache/testing/CacheValidationListener.java
@@ -71,6 +71,8 @@ public void afterInvocation(IInvokedMethod method, ITestResult testResult) {
checkWriter(testResult, context);
checkNoStats(testResult, context);
checkExecutor(testResult, context);
+ } else {
+ testResult.setThrowable(new AssertionError(getTestName(method), testResult.getThrowable()));
}
} catch (Throwable caught) {
testResult.setStatus(ITestResult.FAILURE);
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 65ff117999..206aefc1fe 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
@@ -85,7 +85,8 @@ public static Cache newCaffeineCache(CacheContext context) {
builder.writer(context.cacheWriter());
}
if (context.isAsync()) {
- context.asyncCache = builder.buildAsync(context.loader);
+ context.asyncCache = builder.buildAsync(
+ context.isAsyncLoading ? context.loader.async() : context.loader);
context.cache = context.asyncCache.synchronous();
} else if (context.loader == null) {
context.cache = builder.build();
diff --git a/gradle/dependencies.gradle b/gradle/dependencies.gradle
index 235cc463bb..b450b6587c 100644
--- a/gradle/dependencies.gradle
+++ b/gradle/dependencies.gradle
@@ -36,8 +36,8 @@ ext {
jcache: '1.0.0',
jsr305: '3.0.1',
stream: '2.9.0',
- univocity_parsers: '1.5.6',
- ycsb: '0.6.0',
+ univocity_parsers: '2.0.0',
+ ycsb: '0.7.0-RC1',
]
test_versions = [
awaitility: '1.7.0',
@@ -57,7 +57,7 @@ ext {
ehcache2: '2.10.1-55',
ehcache3: '3.0.0.m4',
high_scale_lib: '1.0.6',
- infinispan: '8.2.0.Beta1',
+ infinispan: '8.2.0.Beta2',
jackrabbit: '1.3.15',
jamm: '0.3.1',
java_object_layout: '0.4',
diff --git a/simulator/src/main/java/com/github/benmanes/caffeine/cache/simulator/Synthetic.java b/simulator/src/main/java/com/github/benmanes/caffeine/cache/simulator/Synthetic.java
index 0296e1b223..8d492780a0 100644
--- a/simulator/src/main/java/com/github/benmanes/caffeine/cache/simulator/Synthetic.java
+++ b/simulator/src/main/java/com/github/benmanes/caffeine/cache/simulator/Synthetic.java
@@ -22,7 +22,7 @@
import com.yahoo.ycsb.generator.CounterGenerator;
import com.yahoo.ycsb.generator.ExponentialGenerator;
import com.yahoo.ycsb.generator.HotspotIntegerGenerator;
-import com.yahoo.ycsb.generator.IntegerGenerator;
+import com.yahoo.ycsb.generator.NumberGenerator;
import com.yahoo.ycsb.generator.ScrambledZipfianGenerator;
import com.yahoo.ycsb.generator.SkewedLatestGenerator;
import com.yahoo.ycsb.generator.UniformIntegerGenerator;
@@ -150,7 +150,7 @@ public static LongStream uniform(int lowerBound, int upperBound, int items) {
}
/** Returns a sequence of items constructed by the generator. */
- private static LongStream generate(IntegerGenerator generator, long count) {
- return LongStream.range(0, count).map(ignored -> generator.nextInt());
+ private static LongStream generate(NumberGenerator generator, long count) {
+ return LongStream.range(0, count).map(ignored -> generator.nextValue().intValue());
}
}