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

ben-manes / caffeine / #5612

11 Jul 2026 03:47PM UTC coverage: 71.292% (-28.7%) from 100.0%
#5612

push

github

ben-manes
Preserve timestamps on a raced same-instance refresh

The automatic refreshIfNeeded path checked for an equal-value reload
before the ABA/ownership check and returned without preserving the
timestamps, so a reload completing after a concurrent put(k,
sameInstance) did a full update — shifting the entry's expiration
forward by the reload's in-flight duration and stomping the write.
Mirror the explicit-refresh fix: check ownership first. An owned
reload installs the value (refreshing metadata even for a same
instance), while a raced reload preserves the write's timestamps and
only notifies REPLACED when the value differs.

2957 of 4134 branches covered (71.53%)

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

2408 existing lines in 53 files now uncovered.

5980 of 8388 relevant lines covered (71.29%)

0.71 hits per line

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

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

18
import static javax.cache.configuration.OptionalFeature.STORE_BY_REFERENCE;
19

20
import java.io.IOException;
21
import java.net.URI;
22
import java.net.URL;
23
import java.util.ArrayList;
24
import java.util.Collections;
25
import java.util.Enumeration;
26
import java.util.HashMap;
27
import java.util.Map;
28
import java.util.Properties;
29
import java.util.WeakHashMap;
30

31
import javax.cache.CacheManager;
32
import javax.cache.Caching;
33
import javax.cache.configuration.OptionalFeature;
34
import javax.cache.spi.CachingProvider;
35

36
import org.jspecify.annotations.NullMarked;
37
import org.jspecify.annotations.Nullable;
38
import org.osgi.service.component.annotations.Activate;
39
import org.osgi.service.component.annotations.Component;
40

41
import com.github.benmanes.caffeine.jcache.CacheManagerImpl;
42
import com.google.errorprone.annotations.Var;
43
import com.google.errorprone.annotations.concurrent.GuardedBy;
44

45
/**
46
 * A provider that produces a JCache implementation backed by Caffeine. Typically, this provider is
47
 * instantiated using {@link Caching#getCachingProvider()} which discovers this implementation
48
 * through a {@link java.util.ServiceLoader}.
49
 * <p>
50
 * This provider is expected to be used for application life cycle events, like initialization. It
51
 * is not expected that all requests flow through the provider to obtain the cache manager and cache
52
 * instances for request operations. Internally, this implementation is synchronized to avoid using
53
 * excess memory due to its infrequent usage.
54
 *
55
 * @author ben.manes@gmail.com (Ben Manes)
56
 */
57
@Component
58
@NullMarked
59
public final class CaffeineCachingProvider implements CachingProvider {
UNCOV
60
  private static final ClassLoader DEFAULT_CLASS_LOADER =
×
UNCOV
61
      new JCacheClassLoader(Thread.currentThread().getContextClassLoader());
×
62

63
  @GuardedBy("itself")
64
  private final Map<ClassLoader, Map<URI, CacheManager>> cacheManagers;
65

66
  boolean isOsgiComponent;
67

UNCOV
68
  public CaffeineCachingProvider() {
×
UNCOV
69
    this.cacheManagers = new WeakHashMap<>(1);
×
UNCOV
70
  }
×
71

72
  @Override
73
  public ClassLoader getDefaultClassLoader() {
UNCOV
74
    return DEFAULT_CLASS_LOADER;
×
75
  }
76

77
  @Override
78
  public URI getDefaultURI() {
UNCOV
79
    return URI.create(getClass().getName());
×
80
  }
81

82
  @Override
83
  public Properties getDefaultProperties() {
UNCOV
84
    return new Properties();
×
85
  }
86

87
  @Override
88
  public CacheManager getCacheManager() {
UNCOV
89
    return getCacheManager(getDefaultURI(), getDefaultClassLoader());
×
90
  }
91

92
  @Override
93
  public CacheManager getCacheManager(URI uri, ClassLoader classLoader) {
UNCOV
94
    return getCacheManager(uri, classLoader, getDefaultProperties());
×
95
  }
96

97
  @Override
98
  public CacheManager getCacheManager(URI uri,
99
      ClassLoader classLoader, @Nullable Properties properties) {
UNCOV
100
    URI managerUri = getManagerUri(uri);
×
UNCOV
101
    ClassLoader managerClassLoader = getManagerClassLoader(classLoader);
×
102

UNCOV
103
    synchronized (cacheManagers) {
×
UNCOV
104
      Map<URI, CacheManager> cacheManagersByUri = cacheManagers.computeIfAbsent(
×
UNCOV
105
          managerClassLoader, any -> new HashMap<>());
×
UNCOV
106
      return cacheManagersByUri.computeIfAbsent(managerUri, any -> {
×
UNCOV
107
        Properties managerProperties = (properties == null) ? getDefaultProperties() : properties;
×
UNCOV
108
        return new CacheManagerImpl(this, isOsgiComponent,
×
109
            managerUri, managerClassLoader, managerProperties);
110
      });
111
    }
112
  }
113

114
  @Override
115
  public void close() {
UNCOV
116
    synchronized (cacheManagers) {
×
UNCOV
117
      for (ClassLoader classLoader : new ArrayList<>(cacheManagers.keySet())) {
×
UNCOV
118
        close(classLoader);
×
UNCOV
119
      }
×
UNCOV
120
    }
×
UNCOV
121
  }
×
122

123
  @Override
124
  @SuppressWarnings("PMD.CloseResource")
125
  public void close(ClassLoader classLoader) {
UNCOV
126
    synchronized (cacheManagers) {
×
UNCOV
127
      ClassLoader managerClassLoader = getManagerClassLoader(classLoader);
×
UNCOV
128
      Map<URI, CacheManager> cacheManagersByUri = cacheManagers.remove(managerClassLoader);
×
UNCOV
129
      if (cacheManagersByUri != null) {
×
UNCOV
130
        for (CacheManager cacheManager : cacheManagersByUri.values()) {
×
UNCOV
131
          cacheManager.close();
×
UNCOV
132
        }
×
133
      }
UNCOV
134
    }
×
UNCOV
135
  }
×
136

137
  @Override
138
  @SuppressWarnings("PMD.CloseResource")
139
  public void close(URI uri, ClassLoader classLoader) {
UNCOV
140
    synchronized (cacheManagers) {
×
UNCOV
141
      ClassLoader managerClassLoader = getManagerClassLoader(classLoader);
×
UNCOV
142
      Map<URI, CacheManager> cacheManagersByUri = cacheManagers.get(managerClassLoader);
×
143

UNCOV
144
      if (cacheManagersByUri != null) {
×
UNCOV
145
        CacheManager cacheManager = cacheManagersByUri.remove(getManagerUri(uri));
×
UNCOV
146
        if (cacheManager != null) {
×
UNCOV
147
          cacheManager.close();
×
148
        }
UNCOV
149
        if (cacheManagersByUri.isEmpty()) {
×
UNCOV
150
          cacheManagers.remove(managerClassLoader);
×
151
        }
152
      }
UNCOV
153
    }
×
UNCOV
154
  }
×
155

156
  @Override
157
  public boolean isSupported(OptionalFeature optionalFeature) {
UNCOV
158
    return (optionalFeature == STORE_BY_REFERENCE);
×
159
  }
160

161
  private URI getManagerUri(@Nullable URI uri) {
UNCOV
162
    return (uri == null) ? getDefaultURI() : uri;
×
163
  }
164

165
  private ClassLoader getManagerClassLoader(@Nullable ClassLoader classLoader) {
UNCOV
166
    return (classLoader == null) ? getDefaultClassLoader() : classLoader;
×
167
  }
168

169
  /**
170
   * A {@link ClassLoader} that combines {@code Thread.currentThread().getContextClassLoader()}
171
   * and {@code getClass().getClassLoader()}.
172
   */
173
  static class JCacheClassLoader extends ClassLoader {
174

175
    public JCacheClassLoader(@Nullable ClassLoader parent) {
UNCOV
176
      super(parent);
×
UNCOV
177
    }
×
178

179
    @Override
180
    public Class<?> loadClass(String name) throws ClassNotFoundException {
UNCOV
181
      @Var ClassNotFoundException error = null;
×
182

UNCOV
183
      ClassLoader contextClassLoader = Thread.currentThread().getContextClassLoader();
×
UNCOV
184
      if ((contextClassLoader != null) && (contextClassLoader != DEFAULT_CLASS_LOADER)) {
×
185
        try {
UNCOV
186
          return contextClassLoader.loadClass(name);
×
UNCOV
187
        } catch (ClassNotFoundException e) {
×
UNCOV
188
          error = e;
×
189
        }
190
      }
191

UNCOV
192
      ClassLoader classClassLoader = getClassClassLoader();
×
UNCOV
193
      if ((classClassLoader != null) && (classClassLoader != contextClassLoader)) {
×
194
        try {
UNCOV
195
          return classClassLoader.loadClass(name);
×
UNCOV
196
        } catch (ClassNotFoundException e) {
×
UNCOV
197
          error = e;
×
198
        }
199
      }
200

UNCOV
201
      ClassLoader parentClassLoader = getParent();
×
UNCOV
202
      if ((parentClassLoader != null)
×
203
          && (parentClassLoader != classClassLoader)
204
          && (parentClassLoader != contextClassLoader)) {
205
        try {
UNCOV
206
          return parentClassLoader.loadClass(name);
×
UNCOV
207
        } catch (ClassNotFoundException e) {
×
UNCOV
208
          error = e;
×
209
        }
210
      }
UNCOV
211
      throw (error == null) ? new ClassNotFoundException(name) : error;
×
212
    }
213

214
    @Override
215
    public @Nullable URL getResource(String name) {
UNCOV
216
      ClassLoader contextClassLoader = Thread.currentThread().getContextClassLoader();
×
UNCOV
217
      if ((contextClassLoader != null) && (contextClassLoader != DEFAULT_CLASS_LOADER)) {
×
UNCOV
218
        URL resource = contextClassLoader.getResource(name);
×
UNCOV
219
        if (resource != null) {
×
UNCOV
220
          return resource;
×
221
        }
222
      }
223

UNCOV
224
      ClassLoader classClassLoader = getClassClassLoader();
×
UNCOV
225
      if ((classClassLoader != null) && (classClassLoader != contextClassLoader)) {
×
UNCOV
226
        URL resource = classClassLoader.getResource(name);
×
UNCOV
227
        if (resource != null) {
×
UNCOV
228
          return resource;
×
229
        }
230
      }
231

UNCOV
232
      ClassLoader parentClassLoader = getParent();
×
UNCOV
233
      if ((parentClassLoader != null)
×
234
          && (parentClassLoader != classClassLoader)
235
          && (parentClassLoader != contextClassLoader)) {
UNCOV
236
        return parentClassLoader.getResource(name);
×
237
      }
238

UNCOV
239
      return null;
×
240
    }
241

242
    @Override
243
    public Enumeration<URL> getResources(String name) throws IOException {
UNCOV
244
      var resources = new ArrayList<URL>();
×
245

UNCOV
246
      ClassLoader contextClassLoader = Thread.currentThread().getContextClassLoader();
×
UNCOV
247
      if ((contextClassLoader != null) && contextClassLoader != DEFAULT_CLASS_LOADER) {
×
UNCOV
248
        resources.addAll(Collections.list(contextClassLoader.getResources(name)));
×
249
      }
250

UNCOV
251
      ClassLoader classClassLoader = getClassClassLoader();
×
UNCOV
252
      if ((classClassLoader != null) && (classClassLoader != contextClassLoader)) {
×
UNCOV
253
        resources.addAll(Collections.list(classClassLoader.getResources(name)));
×
254
      }
255

UNCOV
256
      ClassLoader parentClassLoader = getParent();
×
UNCOV
257
      if ((parentClassLoader != null)
×
258
          && (parentClassLoader != classClassLoader)
259
          && (parentClassLoader != contextClassLoader)) {
UNCOV
260
        resources.addAll(Collections.list(parentClassLoader.getResources(name)));
×
261
      }
262

UNCOV
263
      return Collections.enumeration(resources);
×
264
    }
265

266
    @Nullable ClassLoader getClassClassLoader() {
UNCOV
267
      return getClass().getClassLoader();
×
268
    }
269
  }
270

271
  @Activate
272
  @SuppressWarnings("unused")
273
  private void activate() {
UNCOV
274
    isOsgiComponent = true;
×
UNCOV
275
  }
×
276
}
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