Skip to content

Commit 3a328dc

Browse files
authored
Merge pull request #31755 from mkouba/cache-getasync-optimization
2 parents 7909903 + cde6d9d commit 3a328dc

File tree

1 file changed

+60
-35
lines changed

1 file changed

+60
-35
lines changed

extensions/cache/runtime/src/main/java/io/quarkus/cache/runtime/caffeine/CaffeineCacheImpl.java

Lines changed: 60 additions & 35 deletions
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,6 @@
88
import java.util.Set;
99
import java.util.concurrent.CompletableFuture;
1010
import java.util.concurrent.CompletionStage;
11-
import java.util.function.BiFunction;
1211
import java.util.function.Function;
1312
import java.util.function.Predicate;
1413
import java.util.function.Supplier;
@@ -103,9 +102,21 @@ public <K, V> Uni<V> getAsync(K key, Function<K, Uni<V>> valueLoader) {
103102
.completionStage(new Supplier<CompletionStage<V>>() {
104103
@Override
105104
public CompletionStage<V> get() {
106-
// When stats are enabled we need to use Map.compute() in order to call statsCounter.recordHits(1)
107-
// Map.compute() is more costly compared to Map.computeIfAbsent() because the remapping function is always called and the returned value is replaced
108-
return recordStats ? computeWithStats(key, valueLoader) : computeWithoutStats(key, valueLoader);
105+
// When stats are enabled we need to call statsCounter.recordHits(1)/statsCounter.recordMisses(1) accordingly
106+
StatsRecorder recorder = recordStats ? new OperationalStatsRecorder() : NoopStatsRecorder.INSTANCE;
107+
@SuppressWarnings("unchecked")
108+
CompletionStage<V> result = (CompletionStage<V>) cache.asMap().computeIfAbsent(key,
109+
new Function<Object, CompletableFuture<Object>>() {
110+
@Override
111+
public CompletableFuture<Object> apply(Object key) {
112+
recorder.onValueAbsent();
113+
return valueLoader.apply((K) key)
114+
.map(TO_CACHE_VALUE)
115+
.subscribeAsCompletionStage();
116+
}
117+
});
118+
recorder.doRecord(key);
119+
return result;
109120
}
110121
}).map(fromCacheValue());
111122
}
@@ -126,6 +137,7 @@ public <V> CompletableFuture<V> getIfPresent(Object key) {
126137
// cast, but still throw the CacheException in case it fails
127138
return unwrapCacheValueOrThrowable(existingCacheValue)
128139
.thenApply(new Function<>() {
140+
@SuppressWarnings("unchecked")
129141
@Override
130142
public V apply(Object value) {
131143
try {
@@ -227,6 +239,7 @@ public Set<Object> keySet() {
227239
return Collections.unmodifiableSet(new HashSet<>(cache.asMap().keySet()));
228240
}
229241

242+
@SuppressWarnings("unchecked")
230243
@Override
231244
public <V> void put(Object key, CompletableFuture<V> valueFuture) {
232245
cache.put(key, (CompletableFuture<Object>) valueFuture);
@@ -288,41 +301,53 @@ private <T> T cast(Object value) {
288301
}
289302

290303
@SuppressWarnings("unchecked")
291-
private <K, V> CompletionStage<V> computeWithStats(K key, Function<K, Uni<V>> valueLoader) {
292-
return (CompletionStage<V>) cache.asMap().compute(key,
293-
new BiFunction<Object, CompletableFuture<Object>, CompletableFuture<Object>>() {
294-
@Override
295-
public CompletableFuture<Object> apply(Object key, CompletableFuture<Object> value) {
296-
if (value == null) {
297-
statsCounter.recordMisses(1);
298-
return valueLoader.apply((K) key)
299-
.map(TO_CACHE_VALUE)
300-
.subscribeAsCompletionStage();
301-
} else {
302-
LOGGER.tracef("Key [%s] found in cache [%s]", key, cacheInfo.name);
303-
statsCounter.recordHits(1);
304-
return value;
305-
}
306-
}
307-
});
304+
private <V> Function<V, V> fromCacheValue() {
305+
return (Function<V, V>) FROM_CACHE_VALUE;
308306
}
309307

310-
@SuppressWarnings("unchecked")
311-
private <K, V> CompletionStage<V> computeWithoutStats(K key, Function<K, Uni<V>> valueLoader) {
312-
return (CompletionStage<V>) cache.asMap().computeIfAbsent(key,
313-
new Function<Object, CompletableFuture<Object>>() {
314-
@Override
315-
public CompletableFuture<Object> apply(Object key) {
316-
return valueLoader.apply((K) key)
317-
.map(TO_CACHE_VALUE)
318-
.subscribeAsCompletionStage();
319-
}
320-
});
308+
private interface StatsRecorder {
309+
310+
void onValueAbsent();
311+
312+
<K> void doRecord(K key);
313+
321314
}
322315

323-
@SuppressWarnings("unchecked")
324-
private <V> Function<V, V> fromCacheValue() {
325-
return (Function<V, V>) FROM_CACHE_VALUE;
316+
private static class NoopStatsRecorder implements StatsRecorder {
317+
318+
static final NoopStatsRecorder INSTANCE = new NoopStatsRecorder();
319+
320+
@Override
321+
public void onValueAbsent() {
322+
// no-op
323+
}
324+
325+
@Override
326+
public <K> void doRecord(K key) {
327+
// no-op
328+
}
329+
330+
}
331+
332+
private class OperationalStatsRecorder implements StatsRecorder {
333+
334+
private boolean valueAbsent;
335+
336+
@Override
337+
public void onValueAbsent() {
338+
valueAbsent = true;
339+
}
340+
341+
@Override
342+
public <K> void doRecord(K key) {
343+
if (valueAbsent) {
344+
statsCounter.recordMisses(1);
345+
} else {
346+
LOGGER.tracef("Key [%s] found in cache [%s]", key, cacheInfo.name);
347+
statsCounter.recordHits(1);
348+
}
349+
}
350+
326351
}
327352

328353
private static final Function<Object, Object> FROM_CACHE_VALUE = new Function<Object, Object>() {

0 commit comments

Comments
 (0)