• 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/CacheManagerImpl.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.Objects.requireNonNull;
19

20
import java.lang.System.Logger;
21
import java.lang.System.Logger.Level;
22
import java.lang.ref.WeakReference;
23
import java.net.URI;
24
import java.util.Collection;
25
import java.util.List;
26
import java.util.Map;
27
import java.util.Properties;
28
import java.util.concurrent.ConcurrentHashMap;
29

30
import javax.cache.Cache;
31
import javax.cache.CacheException;
32
import javax.cache.CacheManager;
33
import javax.cache.configuration.CompleteConfiguration;
34
import javax.cache.configuration.Configuration;
35
import javax.cache.spi.CachingProvider;
36

37
import org.jspecify.annotations.Nullable;
38

39
/**
40
 * An implementation of JSR-107 {@link CacheManager} that manages Caffeine-based caches.
41
 *
42
 * @author ben.manes@gmail.com (Ben Manes)
43
 */
44
@SuppressWarnings("PMD.CloseResource")
45
public final class CacheManagerImpl implements CacheManager {
UNCOV
46
  private static final Logger logger = System.getLogger(CacheManagerImpl.class.getName());
×
47

48
  private final Object lock;
49

50
  final WeakReference<ClassLoader> classLoaderReference;
51
  final Map<String, CacheProxy<?, ?>> caches;
52
  final CachingProvider cacheProvider;
53
  final Properties properties;
54
  final URI uri;
55

56
  final boolean runsAsAnOsgiBundle;
57

58
  volatile boolean closed;
59

60
  public CacheManagerImpl(CachingProvider cacheProvider, boolean runsAsAnOsgiBundle,
UNCOV
61
      URI uri, ClassLoader classLoader, Properties properties) {
×
UNCOV
62
    this.classLoaderReference = new WeakReference<>(requireNonNull(classLoader));
×
UNCOV
63
    this.cacheProvider = requireNonNull(cacheProvider);
×
UNCOV
64
    this.runsAsAnOsgiBundle = runsAsAnOsgiBundle;
×
UNCOV
65
    this.properties = requireNonNull(properties);
×
UNCOV
66
    this.caches = new ConcurrentHashMap<>();
×
UNCOV
67
    this.uri = requireNonNull(uri);
×
UNCOV
68
    this.lock = new Object();
×
UNCOV
69
  }
×
70

71
  @Override
72
  public CachingProvider getCachingProvider() {
UNCOV
73
    return cacheProvider;
×
74
  }
75

76
  @Override
77
  public URI getURI() {
UNCOV
78
    return uri;
×
79
  }
80

81
  @Override
82
  public @Nullable ClassLoader getClassLoader() {
UNCOV
83
    return classLoaderReference.get();
×
84
  }
85

86
  @Override
87
  public Properties getProperties() {
UNCOV
88
    return properties;
×
89
  }
90

91
  @Override
92
  public <K, V, C extends Configuration<K, V>> Cache<K, V> createCache(
93
      String cacheName, C configuration) {
UNCOV
94
    requireNonNull(configuration);
×
UNCOV
95
    var classLoader = Thread.currentThread().getContextClassLoader();
×
96
    try {
UNCOV
97
      if (runsAsAnOsgiBundle) {
×
98
        // override the context class loader with the CachingManager's classloader
UNCOV
99
        Thread.currentThread().setContextClassLoader(getClassLoader());
×
100
      }
101

UNCOV
102
      synchronized (lock) {
×
UNCOV
103
        requireNotClosed();
×
UNCOV
104
        CacheProxy<?, ?> cache = caches.compute(cacheName, (name, existing) -> {
×
UNCOV
105
          if (existing != null) {
×
UNCOV
106
            throw new CacheException("Cache " + cacheName + " already exists");
×
UNCOV
107
          } else if (CacheFactory.isDefinedExternally(this, cacheName)) {
×
UNCOV
108
            throw new CacheException("Cache " + cacheName + " is configured externally");
×
109
          }
UNCOV
110
          return CacheFactory.createCache(this, cacheName, configuration);
×
111
        });
112

113
        @SuppressWarnings("unchecked")
UNCOV
114
        var config = cache.getConfiguration(CompleteConfiguration.class);
×
UNCOV
115
        enableManagement(cache.getName(), config.isManagementEnabled());
×
UNCOV
116
        enableStatistics(cache.getName(), config.isStatisticsEnabled());
×
117

118
        @SuppressWarnings("unchecked")
UNCOV
119
        var castedCache = (Cache<K, V>) cache;
×
UNCOV
120
        return castedCache;
×
121
      }
122
    } finally {
UNCOV
123
      Thread.currentThread().setContextClassLoader(classLoader);
×
124
    }
125
  }
126

127
  @Override
128
  public <K, V> @Nullable Cache<K, V> getCache(
129
      String cacheName, Class<K> keyType, Class<V> valueType) {
UNCOV
130
    requireNonNull(keyType);
×
UNCOV
131
    requireNonNull(valueType);
×
UNCOV
132
    var classLoader = Thread.currentThread().getContextClassLoader();
×
133
    try {
UNCOV
134
      if (runsAsAnOsgiBundle) {
×
135
        // override the context class loader with the CachingManager's classloader
UNCOV
136
        Thread.currentThread().setContextClassLoader(getClassLoader());
×
137
      }
UNCOV
138
      CacheProxy<K, V> cache = getCache(cacheName);
×
UNCOV
139
      if (cache == null) {
×
UNCOV
140
        return null;
×
141
      }
142

143
      @SuppressWarnings("unchecked")
UNCOV
144
      var config = cache.getConfiguration(CompleteConfiguration.class);
×
UNCOV
145
      if (keyType != config.getKeyType()) {
×
UNCOV
146
        throw new ClassCastException("Incompatible cache key types specified, expected "
×
UNCOV
147
            + config.getKeyType() + " but " + keyType + " was specified");
×
UNCOV
148
      } else if (valueType != config.getValueType()) {
×
UNCOV
149
        throw new ClassCastException("Incompatible cache value types specified, expected "
×
UNCOV
150
            + config.getValueType() + " but " + valueType + " was specified");
×
151
      }
UNCOV
152
      return cache;
×
153
    } finally {
UNCOV
154
      Thread.currentThread().setContextClassLoader(classLoader);
×
155
    }
156
  }
157

158
  @Override
159
  public <K, V> @Nullable CacheProxy<K, V> getCache(String cacheName) {
UNCOV
160
    requireNonNull(cacheName);
×
UNCOV
161
    var classLoader = Thread.currentThread().getContextClassLoader();
×
162
    try {
UNCOV
163
      if (runsAsAnOsgiBundle) {
×
164
        // override the context class loader with the CachingManager's classloader
UNCOV
165
        Thread.currentThread().setContextClassLoader(getClassLoader());
×
166
      }
167

UNCOV
168
      synchronized (lock) {
×
UNCOV
169
        requireNotClosed();
×
UNCOV
170
        CacheProxy<?, ?> cache = caches.computeIfAbsent(cacheName, name -> {
×
UNCOV
171
          CacheProxy<?, ?> created = CacheFactory.tryToCreateFromExternalSettings(this, name);
×
UNCOV
172
          if (created != null) {
×
173
            @SuppressWarnings("unchecked")
UNCOV
174
            var config = created.getConfiguration(CompleteConfiguration.class);
×
UNCOV
175
            created.enableManagement(config.isManagementEnabled());
×
UNCOV
176
            created.enableStatistics(config.isStatisticsEnabled());
×
177
          }
UNCOV
178
          return created;
×
179
        });
180

181
        @SuppressWarnings("unchecked")
UNCOV
182
        var castedCache = (CacheProxy<K, V>) cache;
×
UNCOV
183
        return castedCache;
×
184
      }
185
    } finally {
UNCOV
186
      Thread.currentThread().setContextClassLoader(classLoader);
×
187
    }
188
  }
189

190
  @Override
191
  @SuppressWarnings("PreferredInterfaceType")
192
  public Collection<String> getCacheNames() {
UNCOV
193
    requireNotClosed();
×
UNCOV
194
    return List.copyOf(caches.keySet());
×
195
  }
196

197
  @Override
198
  public void destroyCache(String cacheName) {
UNCOV
199
    requireNotClosed();
×
200

UNCOV
201
    Cache<?, ?> cache = caches.remove(cacheName);
×
UNCOV
202
    if (cache != null) {
×
UNCOV
203
      cache.close();
×
204
    }
UNCOV
205
  }
×
206

207
  /** Removes the cache from the registry if it is still the registered instance. */
208
  void destroyCache(String cacheName, CacheProxy<?, ?> cache) {
UNCOV
209
    requireNotClosed();
×
UNCOV
210
    caches.remove(cacheName, cache);
×
UNCOV
211
  }
×
212

213
  @Override
214
  public void enableManagement(String cacheName, boolean enabled) {
UNCOV
215
    requireNotClosed();
×
216

UNCOV
217
    CacheProxy<?, ?> cache = caches.get(cacheName);
×
UNCOV
218
    if (cache == null) {
×
UNCOV
219
      return;
×
220
    }
UNCOV
221
    cache.enableManagement(enabled);
×
UNCOV
222
  }
×
223

224
  @Override
225
  public void enableStatistics(String cacheName, boolean enabled) {
UNCOV
226
    requireNotClosed();
×
227

UNCOV
228
    CacheProxy<?, ?> cache = caches.get(cacheName);
×
UNCOV
229
    if (cache == null) {
×
UNCOV
230
      return;
×
231
    }
UNCOV
232
    cache.enableStatistics(enabled);
×
UNCOV
233
  }
×
234

235
  @Override
236
  public void close() {
UNCOV
237
    if (isClosed()) {
×
UNCOV
238
      return;
×
239
    }
UNCOV
240
    synchronized (lock) {
×
UNCOV
241
      if (isClosed()) {
×
UNCOV
242
        return;
×
243
      }
UNCOV
244
      for (Cache<?, ?> cache : caches.values()) {
×
245
        try {
UNCOV
246
          cache.close();
×
UNCOV
247
        } catch (RuntimeException e) {
×
UNCOV
248
          logger.log(Level.WARNING, "Exception thrown when closing cache " + cache.getName(), e);
×
UNCOV
249
        }
×
UNCOV
250
      }
×
UNCOV
251
      closed = true;
×
UNCOV
252
    }
×
UNCOV
253
    ClassLoader classLoader = classLoaderReference.get();
×
UNCOV
254
    if (classLoader != null) {
×
UNCOV
255
      cacheProvider.close(uri, classLoader);
×
256
    }
UNCOV
257
  }
×
258

259
  @Override
260
  public boolean isClosed() {
UNCOV
261
    return closed;
×
262
  }
263

264
  @Override
265
  public <T> T unwrap(Class<T> clazz) {
UNCOV
266
    if (clazz.isInstance(this)) {
×
UNCOV
267
      return clazz.cast(this);
×
268
    }
UNCOV
269
    throw new IllegalArgumentException("Unwrapping to " + clazz
×
270
        + " is not supported by this implementation");
271
  }
272

273
  /** Checks that the cache manager is not closed. */
274
  private void requireNotClosed() {
UNCOV
275
    if (isClosed()) {
×
UNCOV
276
      throw new IllegalStateException();
×
277
    }
UNCOV
278
  }
×
279
}
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