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

ben-manes / caffeine / #5698

30 Jul 2026 09:29AM UTC coverage: 99.918% (-0.07%) from 99.988%
#5698

push

github

ben-manes
Fix minor defects found by a review sweep

- Prefer the operation's own failure over a throwing synchronous jcache
  listener's, retaining the latter as suppressed. awaitSynchronous()
  became throwing, so putAll, removeAll, invoke, and LoadingCacheProxy's
  get and getAll discarded the writer, loader, or entry processor
  failure that the specification requires the caller to observe.
- Chain the per-key dispatch queue with handleAsync so that a
  predecessor which completed exceptionally, as dispatch rethrows an
  Error, cannot suppress the next event's delivery. thenApplyAsync
  relayed the throwable and silently never invoked the listener, losing
  a same-key event. The predecessor's failure is still observed through
  the pending list, which holds each synchronous event's own future.
- Guard the synchronous listener exception fold against self-
  suppression, as a listener that rethrows a stored exception yields the
  same instance for two events and Throwable.addSuppressed then throws,
  masking the failure it was folding. Restore awaitSynchronous()'s fast
  path for when nothing is pending, which delegating to
  chainSynchronous() had dropped.
- Span the CompletionListener notification with loadAll's tracked future
  so that close() awaits the callback rather than returning while it is
  still queued, and clear the pending synchronous futures in a finally
  so an Error cannot strand them for the next load to join and
  misreport. The notification is composed onto the load rather than
  joined, as its dispatches run on the executor that the load itself
  occupies.
- Unregister the JMX beans before destroyCache frees the cache name, as
  otherwise a same-named replacement cache's beans are unregistered
  instead, and route them through the toggles so that the statistics and
  configuration flags are applied and a failing unregistration is
  aggregated rather than aborting the close. Share the suppression logic
  as suppress(), which tolerates a nu... (continued)

4221 of 4239 branches covered (99.58%)

83 of 88 new or added lines in 7 files covered. (94.32%)

1 existing line in 1 file now uncovered.

8523 of 8530 relevant lines covered (99.92%)

1.0 hits per line

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

97.18
/jcache/src/main/java/com/github/benmanes/caffeine/jcache/LoadingCacheProxy.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.jcache;
17

18
import static java.util.stream.Collectors.toUnmodifiableList;
19

20
import java.util.List;
21
import java.util.Map;
22
import java.util.Optional;
23
import java.util.Set;
24
import java.util.concurrent.Executor;
25

26
import javax.cache.Cache;
27
import javax.cache.CacheException;
28
import javax.cache.expiry.ExpiryPolicy;
29
import javax.cache.integration.CacheLoader;
30

31
import org.jspecify.annotations.Nullable;
32

33
import com.github.benmanes.caffeine.cache.LoadingCache;
34
import com.github.benmanes.caffeine.cache.Ticker;
35
import com.github.benmanes.caffeine.jcache.configuration.CaffeineConfiguration;
36
import com.github.benmanes.caffeine.jcache.copy.Copier;
37
import com.github.benmanes.caffeine.jcache.event.EventDispatcher;
38
import com.github.benmanes.caffeine.jcache.management.JCacheStatisticsMXBean;
39
import com.google.errorprone.annotations.Var;
40

41
/**
42
 * An implementation of JSR-107 {@link Cache} backed by a Caffeine loading cache.
43
 *
44
 * @author ben.manes@gmail.com (Ben Manes)
45
 */
46
@SuppressWarnings("OvershadowingSubclassFields")
47
public final class LoadingCacheProxy<K, V> extends CacheProxy<K, V> {
48
  private final LoadingCache<K, @Nullable Expirable<V>> cache;
49

50
  @SuppressWarnings({"PMD.ExcessiveParameterList", "TooManyParameters"})
51
  public LoadingCacheProxy(String name, Executor executor, CacheManagerImpl cacheManager,
52
      CaffeineConfiguration<K, V> configuration, LoadingCache<K, @Nullable Expirable<V>> cache,
53
      EventDispatcher<K, V> dispatcher, CacheLoader<K, V> cacheLoader, ExpiryPolicy expiry,
54
      Ticker ticker, JCacheStatisticsMXBean statistics, Copier copier) {
55
    super(name, executor, cacheManager, configuration, cache, dispatcher,
1✔
56
        Optional.of(cacheLoader), expiry, ticker, statistics, copier);
1✔
57
    this.cache = cache;
1✔
58
  }
1✔
59

60
  @Override
61
  public @Nullable V get(K key) {
62
    requireNotClosed();
1✔
63
    @Nullable V value;
64
    try {
65
      value = getOrLoad(key);
1✔
66
    } catch (NullPointerException | IllegalStateException | ClassCastException | CacheException e) {
1✔
67
      awaitAndSuppressFailure(e);
1✔
68
      throw e;
1✔
69
    } catch (RuntimeException e) {
1✔
70
      var error = new CacheException(e);
1✔
71
      awaitAndSuppressFailure(error);
1✔
72
      throw error;
1✔
73
    }
1✔
74
    var listenerFailure = awaitSynchronousFailure();
1✔
75
    if (listenerFailure != null) {
1!
NEW
76
      throw listenerFailure;
×
77
    }
78
    return value;
1✔
79
  }
80

81
  /** Retrieves the value from the cache, loading it if necessary. */
82
  private @Nullable V getOrLoad(K key) {
83
    boolean statsEnabled = statistics.isEnabled();
1✔
84
    long start = statsEnabled ? ticker.read() : 0L;
1✔
85

86
    @Var long millis = 0L;
1✔
87
    @Var Expirable<V> expirable = cache.getIfPresent(key);
1✔
88
    if ((expirable != null) && !expirable.isEternal()) {
1✔
89
      millis = nanosToMillis((start == 0L) ? ticker.read() : start);
1✔
90
      if (expirable.hasExpired(millis)) {
1✔
91
        var expired = expirable;
1✔
92
        cache.asMap().computeIfPresent(key, (k, e) -> {
1✔
93
          if (e == expired) {
1✔
94
            dispatcher.publishExpired(this, key, expired.get());
1✔
95
            statistics.recordEvictions(1L);
1✔
96
            return null;
1✔
97
          }
98
          return e;
1✔
99
        });
100
        expirable = null;
1✔
101
      }
102
    }
103

104
    if (expirable == null) {
1✔
105
      statistics.recordMisses(1L);
1✔
106
      expirable = cache.get(copyOf(key));
1✔
107
    } else {
108
      var duration = getAccessExpireTime();
1✔
109
      setVariableExpiration(key, duration);
1✔
110
      setAccessExpireTime(expirable, duration, millis);
1✔
111
      statistics.recordHits(1L);
1✔
112
    }
113

114
    @Var V value = null;
1✔
115
    if (expirable != null) {
1✔
116
      value = copyOf(expirable.get());
1✔
117
    }
118
    if (statsEnabled) {
1✔
119
      statistics.recordGetTime(ticker.read() - start);
1✔
120
    }
121
    return value;
1✔
122
  }
123

124
  @Override
125
  public Map<K, V> getAll(Set<? extends K> keys) {
126
    requireNotClosed();
1✔
127
    boolean statsEnabled = statistics.isEnabled();
1✔
128
    long start = statsEnabled ? ticker.read() : 0L;
1✔
129
    Map<K, V> result;
130
    try {
131
      Map<K, Expirable<V>> entries = getAndFilterExpiredEntries(keys);
1✔
132

133
      if (entries.size() != keys.size()) {
1✔
134
        List<K> keysToLoad = keys.stream()
1✔
135
            .filter(key -> !entries.containsKey(key))
1✔
136
            .map(this::copyOf)
1✔
137
            .collect(toUnmodifiableList());
1✔
138
        entries.putAll(cache.getAll(keysToLoad));
1✔
139
      }
140

141
      result = copyMap(entries);
1✔
142
      if (statsEnabled) {
1✔
143
        statistics.recordGetTime(ticker.read() - start);
1✔
144
      }
145
    } catch (NullPointerException | IllegalStateException | ClassCastException | CacheException e) {
1✔
146
      awaitAndSuppressFailure(e);
1✔
147
      throw e;
1✔
148
    } catch (RuntimeException e) {
1✔
149
      var error = new CacheException(e);
1✔
150
      awaitAndSuppressFailure(error);
1✔
151
      throw error;
1✔
152
    }
1✔
153
    var listenerFailure = awaitSynchronousFailure();
1✔
154
    if (listenerFailure != null) {
1!
NEW
155
      throw listenerFailure;
×
156
    }
157
    return result;
1✔
158
  }
159
}
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