• Home
  • Features
  • Pricing
  • Docs
  • Announcements
  • Sign In

ben-manes / caffeine / #5707

02 Aug 2026 12:45AM UTC coverage: 0.106% (-99.9%) from 100.0%
#5707

push

github

ben-manes
fix wikibench trace reader after overly strict audit fixes

0 of 4235 branches covered (0.0%)

9 of 8528 relevant lines covered (0.11%)

0.0 hits per line

Source File
Press 'n' to go to next uncovered line, 'b' for previous

0.0
/caffeine/src/main/java/com/github/benmanes/caffeine/cache/LocalAsyncLoadingCache.java
1
/*
2
 * Copyright 2015 Ben Manes. All Rights Reserved.
3
 *
4
 * Licensed under the Apache License, Version 2.0 (the "License");
5
 * you may not use this file except in compliance with the License.
6
 * You may obtain a copy of the License at
7
 *
8
 *     http://www.apache.org/licenses/LICENSE-2.0
9
 *
10
 * Unless required by applicable law or agreed to in writing, software
11
 * distributed under the License is distributed on an "AS IS" BASIS,
12
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13
 * See the License for the specific language governing permissions and
14
 * limitations under the License.
15
 */
16
package com.github.benmanes.caffeine.cache;
17

18
import static com.github.benmanes.caffeine.cache.Caffeine.calculateHashMapCapacity;
19
import static com.github.benmanes.caffeine.cache.Caffeine.hasMethodOverride;
20
import static com.github.benmanes.caffeine.cache.LocalAsyncCache.composeResult;
21
import static java.util.Objects.requireNonNull;
22

23
import java.lang.System.Logger;
24
import java.lang.System.Logger.Level;
25
import java.util.LinkedHashMap;
26
import java.util.Map;
27
import java.util.Set;
28
import java.util.concurrent.CancellationException;
29
import java.util.concurrent.CompletableFuture;
30
import java.util.concurrent.CompletionException;
31
import java.util.concurrent.Executor;
32
import java.util.concurrent.TimeoutException;
33
import java.util.function.BiFunction;
34

35
import org.jspecify.annotations.Nullable;
36

37
import com.google.errorprone.annotations.Var;
38

39
/**
40
 * This class provides a skeletal implementation of the {@link AsyncLoadingCache} interface to
41
 * minimize the effort required to implement a {@link LocalCache}.
42
 *
43
 * @author ben.manes@gmail.com (Ben Manes)
44
 */
45
abstract class LocalAsyncLoadingCache<K, V>
46
    implements LocalAsyncCache<K, V>, AsyncLoadingCache<K, V> {
47
  static final Logger logger = System.getLogger(LocalAsyncLoadingCache.class.getName());
×
48

49
  final @Nullable BiFunction<? super Set<? extends K>, ? super Executor,
50
      ? extends CompletableFuture<? extends Map<? extends K, ? extends V>>> bulkMappingFunction;
51
  final BiFunction<? super K, ? super Executor,
52
      ? extends CompletableFuture<? extends V>> mappingFunction;
53
  final AsyncCacheLoader<K, V> cacheLoader;
54

55
  @Nullable LoadingCacheView<K, V> cacheView;
56

57
  @SuppressWarnings("unchecked")
58
  LocalAsyncLoadingCache(AsyncCacheLoader<? super K, V> cacheLoader) {
×
59
    this.bulkMappingFunction = newBulkMappingFunction(cacheLoader);
×
60
    this.cacheLoader = (AsyncCacheLoader<K, V>) cacheLoader;
×
61
    this.mappingFunction = newMappingFunction(cacheLoader);
×
62
  }
×
63

64
  /** Returns a mapping function that adapts to {@link AsyncCacheLoader#asyncLoad}. */
65
  BiFunction<
66
      ? super K,
67
      ? super Executor,
68
      ? extends CompletableFuture<? extends V>> newMappingFunction(
69
          AsyncCacheLoader<? super K, V> cacheLoader) {
70
    return (key, executor) -> {
×
71
      try {
72
        return cacheLoader.asyncLoad(key, executor);
×
73
      } catch (RuntimeException e) {
×
74
        throw e;
×
75
      } catch (InterruptedException e) {
×
76
        Thread.currentThread().interrupt();
×
77
        throw new CompletionException(e);
×
78
      } catch (Exception e) {
×
79
        throw new CompletionException(e);
×
80
      }
81
    };
82
  }
83

84
  /**
85
   * Returns a mapping function that adapts to {@link AsyncCacheLoader#asyncLoadAll}, if
86
   * implemented.
87
   */
88
  @Nullable BiFunction<Set<? extends K>, Executor, CompletableFuture<Map<K, V>>>
89
      newBulkMappingFunction(AsyncCacheLoader<? super K, V> cacheLoader) {
90
    if (!canBulkLoad(cacheLoader)) {
×
91
      return null;
×
92
    }
93
    return (keysToLoad, executor) -> {
×
94
      try {
95
        @SuppressWarnings("unchecked")
96
        var loaded = (CompletableFuture<Map<K, V>>) cacheLoader.asyncLoadAll(keysToLoad, executor);
×
97
        return loaded;
×
98
      } catch (RuntimeException e) {
×
99
        throw e;
×
100
      } catch (InterruptedException e) {
×
101
        Thread.currentThread().interrupt();
×
102
        throw new CompletionException(e);
×
103
      } catch (Exception e) {
×
104
        throw new CompletionException(e);
×
105
      }
106
    };
107
  }
108

109
  /** Returns whether the supplied cache loader has bulk load functionality. */
110
  boolean canBulkLoad(AsyncCacheLoader<?, ?> loader) {
111
    @Var Class<?> defaultLoaderClass = AsyncCacheLoader.class;
×
112
    if (loader instanceof CacheLoader<?, ?>) {
×
113
      defaultLoaderClass = CacheLoader.class;
×
114
      if (hasMethodOverride(defaultLoaderClass, loader, "loadAll", Set.class)) {
×
115
        return true;
×
116
      }
117
    }
118
    return hasMethodOverride(defaultLoaderClass,
×
119
        loader, "asyncLoadAll", Set.class, Executor.class);
120
  }
121

122
  @Override
123
  public CompletableFuture<V> get(K key) {
124
    return get(key, mappingFunction);
×
125
  }
126

127
  @Override
128
  public CompletableFuture<Map<K, V>> getAll(Iterable<? extends K> keys) {
129
    if (bulkMappingFunction != null) {
×
130
      return getAll(keys, bulkMappingFunction);
×
131
    }
132

133
    int initialCapacity = calculateHashMapCapacity(keys);
×
134
    var result = new LinkedHashMap<K, @Nullable CompletableFuture<@Nullable V>>(initialCapacity);
×
135
    for (K key : keys) {
×
136
      result.put(requireNonNull(key), null);
×
137
    }
×
138
    for (var entry : result.entrySet()) {
×
139
      entry.setValue(requireNonNull(get(entry.getKey())));
×
140
    }
×
141

142
    @SuppressWarnings("NullAway")
143
    Map<K, CompletableFuture<@Nullable V>> results = result;
×
144
    return composeResult(results);
×
145
  }
146

147
  @Override
148
  public LoadingCache<K, V> synchronous() {
149
    return (cacheView == null) ? (cacheView = new LoadingCacheView<>(this)) : cacheView;
×
150
  }
151

152
  /* --------------- Synchronous views --------------- */
153

154
  static final class LoadingCacheView<K, V>
155
      extends AbstractCacheView<K, V> implements LoadingCache<K, V> {
156
    private static final long serialVersionUID = 1L;
157

158
    @SuppressWarnings("serial")
159
    final LocalAsyncLoadingCache<K, V> asyncCache;
160

161
    LoadingCacheView(LocalAsyncLoadingCache<K, V> asyncCache) {
×
162
      this.asyncCache = requireNonNull(asyncCache);
×
163
    }
×
164

165
    @Override
166
    LocalAsyncLoadingCache<K, V> asyncCache() {
167
      return asyncCache;
×
168
    }
169

170
    @Override
171
    public V get(K key) {
172
      return resolve(asyncCache.get(key));
×
173
    }
174

175
    @Override
176
    public Map<K, V> getAll(Iterable<? extends K> keys) {
177
      return resolve(asyncCache.getAll(keys));
×
178
    }
179

180
    @Override
181
    public CompletableFuture<V> refresh(K key) {
182
      requireNonNull(key);
×
183

184
      Object keyReference = asyncCache.cache().referenceKey(key);
×
185
      for (;;) {
186
        @Var var future = tryOptimisticRefresh(key, keyReference);
×
187
        if (future == null) {
×
188
          future = tryComputeRefresh(key, keyReference);
×
189
        }
190
        if (future != null) {
×
191
          return future;
×
192
        }
193
      }
×
194
    }
195

196
    @Override
197
    public CompletableFuture<Map<K, V>> refreshAll(Iterable<? extends K> keys) {
198
      var result = new LinkedHashMap<K, CompletableFuture<@Nullable V>>(
×
199
          calculateHashMapCapacity(keys));
×
200
      for (K key : keys) {
×
201
        result.computeIfAbsent(key, this::refresh);
×
202
      }
×
203
      return composeResult(result);
×
204
    }
205

206
    /** Attempts to avoid a reload if the entry is absent, or a load or reload is in-flight. */
207
    @SuppressWarnings("FutureReturnValueIgnored")
208
    private @Nullable CompletableFuture<V> tryOptimisticRefresh(K key, Object keyReference) {
209
      // If a refresh is in-flight, then return it directly. If completed and not yet removed, then
210
      // remove to trigger a new reload.
211
      @SuppressWarnings("unchecked")
212
      var lastRefresh = (CompletableFuture<V>) asyncCache.cache().refreshes().get(keyReference);
×
213
      if (lastRefresh != null) {
×
214
        if (Async.isReady(lastRefresh) || asyncCache.cache().isPendingEviction(key)) {
×
215
          asyncCache.cache().refreshes().remove(keyReference, lastRefresh);
×
216
        } else {
217
          return lastRefresh;
×
218
        }
219
      }
220

221
      // If the entry is absent then perform a new load, else if in-flight then return it
222
      var oldValueFuture = asyncCache.cache().getIfPresentQuietly(key);
×
223
      if ((oldValueFuture == null)
×
224
          || (oldValueFuture.isDone() && oldValueFuture.isCompletedExceptionally())) {
×
225
        if (oldValueFuture != null) {
×
226
          asyncCache.cache().remove(key, oldValueFuture);
×
227
        }
228
        var future = asyncCache.get(key, asyncCache.mappingFunction, /* recordStats= */ false);
×
229
        @SuppressWarnings("unchecked")
230
        var prior = (CompletableFuture<V>) asyncCache.cache()
×
231
            .refreshes().putIfAbsent(keyReference, future);
×
232
        var result = (prior == null) ? future : prior;
×
233
        result.whenComplete((r, e) -> asyncCache.cache().refreshes().remove(keyReference, result));
×
234
        return result;
×
235
      } else if (!oldValueFuture.isDone()) {
×
236
        // no-op if load is pending
237
        return oldValueFuture;
×
238
      }
239

240
      // Fallback to the slow path, possibly retrying
241
      return null;
×
242
    }
243

244
    /** Begins a refresh if the entry has materialized and no reload is in-flight. */
245
    @SuppressWarnings("FutureReturnValueIgnored")
246
    private @Nullable CompletableFuture<V> tryComputeRefresh(K key, Object keyReference) {
247
      var startTime = new long[1];
×
248
      var refreshed = new boolean[1];
×
249
      @SuppressWarnings({"rawtypes", "unchecked"})
250
      @Nullable CompletableFuture<V>[] oldValueFuture = new CompletableFuture[1];
×
251
      var future = asyncCache.cache().refreshes().computeIfAbsent(keyReference, k -> {
×
252
        oldValueFuture[0] = asyncCache.cache().getIfPresentQuietly(key);
×
253
        V oldValue = Async.getIfReady(oldValueFuture[0]);
×
254
        if (oldValue == null) {
×
255
          return null;
×
256
        }
257

258
        refreshed[0] = true;
×
259
        startTime[0] = asyncCache.cache().statsTicker().read();
×
260
        try {
261
          var reloadFuture = asyncCache.cacheLoader.asyncReload(
×
262
              key, oldValue, asyncCache.cache().executor());
×
263
          return requireNonNull(reloadFuture, "Null future");
×
264
        } catch (RuntimeException e) {
×
265
          throw e;
×
266
        } catch (InterruptedException e) {
×
267
          Thread.currentThread().interrupt();
×
268
          throw new CompletionException(e);
×
269
        } catch (Exception e) {
×
270
          throw new CompletionException(e);
×
271
        }
272
      });
273

274
      if (future == null) {
×
275
        // Retry the optimistic path
276
        return null;
×
277
      }
278

279
      @SuppressWarnings("unchecked")
280
      var castedFuture = (CompletableFuture<V>) future;
×
281
      if (refreshed[0]) {
×
282
        castedFuture.whenComplete((newValue, error) -> {
×
283
          long loadTime = asyncCache.cache().statsTicker().read() - startTime[0];
×
284
          if (error != null) {
×
285
            if (!(error instanceof CancellationException) && !(error instanceof TimeoutException)) {
×
286
              logger.log(Level.WARNING, "Exception thrown during refresh", error);
×
287
            }
288
            asyncCache.cache().refreshes().remove(keyReference, castedFuture);
×
289
            asyncCache.cache().statsCounter().recordLoadFailure(loadTime);
×
290
            return;
×
291
          }
292

293
          try {
294
            var discard = new boolean[1];
×
295
            var hints = new LocalCache.RemapHints();
×
296
            var value = asyncCache.cache().compute(key, (ignored, currentValue) -> {
×
297
              // Keep the refresh registered until the write clears it to avoid refreshAfterWrite
298
              // readers from prematurely scheduling another reload
299
              boolean successful =
×
300
                  (asyncCache.cache().refreshes().get(keyReference) == castedFuture);
×
301
              if (successful && (currentValue == oldValueFuture[0])) {
×
302
                return (newValue == null) ? null : castedFuture;
×
303
              }
304
              // Otherwise, a write invalidated the refresh so discard it. If a successor refresh
305
              // owns the registration, leave it intact so this stale completion's by-key discard
306
              // cannot steal the successor's token (its fresh value would then be discarded).
307
              hints.preserveTimestamps = true;
×
308
              hints.preserveRefresh = !successful;
×
309
              discard[0] = (newValue != Async.getIfReady((CompletableFuture<?>) currentValue));
×
310
              return currentValue;
×
311
            }, asyncCache.cache().expiry(), /* recordLoad= */ false,
×
312
                /* recordLoadFailure= */ true, hints);
313

314
            if (discard[0] && (newValue != null)) {
×
315
              var cause = (value == null) ? RemovalCause.EXPLICIT : RemovalCause.REPLACED;
×
316
              asyncCache.cache().notifyRemoval(key, castedFuture, cause);
×
317
            }
318
            if (newValue == null) {
×
319
              asyncCache.cache().statsCounter().recordLoadFailure(loadTime);
×
320
            } else {
321
              asyncCache.cache().statsCounter().recordLoadSuccess(loadTime);
×
322
            }
323
          } catch (Throwable t) {
×
324
            logger.log(Level.WARNING, "Exception thrown during refresh", t);
×
325
            asyncCache.cache().statsCounter().recordLoadFailure(loadTime);
×
326
            asyncCache.cache().remove(key, castedFuture);
×
327
          }
×
328
        });
×
329
      }
330
      return castedFuture;
×
331
    }
332
  }
333
}
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE TRIAL · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2026 Coveralls, Inc