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

ben-manes / caffeine / #5449

10 May 2026 06:04PM UTC coverage: 99.903% (+0.001%) from 99.902%
#5449

push

github

ben-manes
Skip cacheProvider.close on a GC'd ClassLoader

CacheManagerImpl held its ClassLoader via WeakReference. If the loader
was GC'd while the manager remained reachable, close() forwarded null
to cacheProvider.close(uri, ...), which fell back to the default
ClassLoader and could close an unrelated peer manager registered under
the default loader for the same URI. Read the WeakReference once and
skip the forward when the loader is gone — the provider's WeakHashMap
entry has already been expunged, so there's nothing to remove there.

issue found by detail.dev

3990 of 3998 branches covered (99.8%)

3 of 3 new or added lines in 1 file covered. (100.0%)

8 existing lines in 2 files now uncovered.

8201 of 8209 relevant lines covered (99.9%)

1.0 hits per line

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

95.65
/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.Objects;
23
import java.util.Optional;
24
import java.util.Set;
25
import java.util.concurrent.CompletableFuture;
26
import java.util.concurrent.Executor;
27

28
import javax.cache.Cache;
29
import javax.cache.CacheException;
30
import javax.cache.CacheManager;
31
import javax.cache.expiry.ExpiryPolicy;
32
import javax.cache.integration.CacheLoader;
33
import javax.cache.integration.CompletionListener;
34

35
import org.jspecify.annotations.Nullable;
36

37
import com.github.benmanes.caffeine.cache.LoadingCache;
38
import com.github.benmanes.caffeine.cache.Ticker;
39
import com.github.benmanes.caffeine.jcache.configuration.CaffeineConfiguration;
40
import com.github.benmanes.caffeine.jcache.event.EventDispatcher;
41
import com.github.benmanes.caffeine.jcache.management.JCacheStatisticsMXBean;
42
import com.google.errorprone.annotations.Var;
43

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

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

63
  @Override
64
  public @Nullable V get(K key) {
65
    requireNotClosed();
1✔
66
    try {
67
      return getOrLoad(key);
1✔
68
    } catch (NullPointerException | IllegalStateException | ClassCastException | CacheException e) {
1✔
69
      throw e;
1✔
70
    } catch (RuntimeException e) {
1✔
71
      throw new CacheException(e);
1✔
72
    } finally {
73
      dispatcher.awaitSynchronous();
1✔
74
    }
75
  }
76

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

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

100
    if (expirable == null) {
1✔
101
      expirable = cache.get(key);
1✔
102
      statistics.recordMisses(1L);
1✔
103
    } else {
104
      setAccessExpireTime(key, expirable, millis);
1✔
105
      statistics.recordHits(1L);
1✔
106
    }
107

108
    @Var V value = null;
1✔
109
    if (expirable != null) {
1✔
110
      value = copyValue(expirable);
1✔
111
    }
112
    if (statsEnabled) {
1✔
113
      statistics.recordGetTime(ticker.read() - start);
1✔
114
    }
115
    return value;
1✔
116
  }
117

118
  @Override
119
  public Map<K, V> getAll(Set<? extends K> keys) {
120
    return getAll(keys, /* updateAccessTime= */ true);
1✔
121
  }
122

123
  /** Returns the entries, loading if necessary, and optionally updates their access expiry time. */
124
  private Map<K, V> getAll(Set<? extends K> keys, boolean updateAccessTime) {
125
    requireNotClosed();
1✔
126
    boolean statsEnabled = statistics.isEnabled();
1✔
127
    long start = statsEnabled ? ticker.read() : 0L;
1✔
128
    try {
129
      Map<K, Expirable<V>> entries = getAndFilterExpiredEntries(keys, updateAccessTime);
1✔
130

131
      if (entries.size() != keys.size()) {
1✔
132
        List<K> keysToLoad = keys.stream()
1✔
133
            .filter(key -> !entries.containsKey(key))
1✔
134
            .collect(toUnmodifiableList());
1✔
135
        entries.putAll(cache.getAll(keysToLoad));
1✔
136
      }
137

138
      Map<K, V> result = copyMap(entries);
1✔
139
      if (statsEnabled) {
1✔
140
        statistics.recordGetTime(ticker.read() - start);
1✔
141
      }
142
      return result;
1✔
143
    } catch (NullPointerException | IllegalStateException | ClassCastException | CacheException e) {
1✔
144
      throw e;
1✔
145
    } catch (RuntimeException e) {
1✔
146
      throw new CacheException(e);
1✔
147
    } finally {
148
      dispatcher.awaitSynchronous();
1✔
149
    }
150
  }
151

152
  @Override
153
  @SuppressWarnings({"CheckReturnValue", "CollectionUndefinedEquality",
154
      "FutureReturnValueIgnored", "ResultOfMethodCallIgnored"})
155
  public void loadAll(Set<? extends K> keys, boolean replaceExistingValues,
156
      @Nullable CompletionListener completionListener) {
157
    requireNotClosed();
1✔
158
    keys.forEach(Objects::requireNonNull);
1✔
159
    CompletionListener listener = (completionListener == null)
1✔
160
        ? NullCompletionListener.INSTANCE
1✔
161
        : completionListener;
1✔
162

163
    var future = new CompletableFuture<@Nullable Void>();
1✔
164
    synchronized (configuration) {
1✔
165
      requireNotClosed();
1✔
166
      inFlight.add(future);
1✔
167
    }
1✔
168
    try {
169
      CompletableFuture.runAsync(() -> {
1✔
170
        try {
171
          if (replaceExistingValues) {
1✔
172
            Map<K, V> loaded = cacheLoader.orElseThrow().loadAll(keys);
1✔
173
            for (var entry : loaded.entrySet()) {
1✔
174
              putNoCopyOrAwait(entry.getKey(), entry.getValue(), /* publishToWriter= */ false);
1✔
175
            }
1✔
176
          } else {
1✔
177
            getAll(keys, /* updateAccessTime= */ false);
1✔
178
          }
179
          listener.onCompletion();
1✔
180
        } catch (RuntimeException e) {
1✔
181
          listener.onException(e);
1✔
182
        } finally {
183
          dispatcher.ignoreSynchronous();
1✔
184
        }
185
      }, executor).whenComplete((r, e) -> {
1✔
186
        inFlight.remove(future);
1✔
187
        future.complete(null);
1✔
188
      });
1✔
189
    } catch (RuntimeException e) {
1✔
190
      inFlight.remove(future);
1✔
191
      future.complete(null);
1✔
192
      listener.onException(e);
1✔
UNCOV
193
    } catch (Throwable t) {
×
UNCOV
194
      inFlight.remove(future);
×
UNCOV
195
      future.complete(null);
×
UNCOV
196
      throw t;
×
197
    }
1✔
198
  }
1✔
199
}
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2026 Coveralls, Inc