• 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

63.04
/guava/src/main/java/com/github/benmanes/caffeine/guava/CaffeinatedGuavaCache.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.guava;
17

18
import static java.util.Objects.requireNonNull;
19

20
import java.io.Serializable;
21
import java.util.Collection;
22
import java.util.Map;
23
import java.util.Map.Entry;
24
import java.util.Objects;
25
import java.util.Set;
26
import java.util.Spliterator;
27
import java.util.concurrent.Callable;
28
import java.util.concurrent.ConcurrentMap;
29
import java.util.concurrent.ExecutionException;
30
import java.util.function.BiConsumer;
31
import java.util.function.BiFunction;
32
import java.util.function.Consumer;
33
import java.util.function.Function;
34
import java.util.function.Predicate;
35

36
import org.jspecify.annotations.Nullable;
37

38
import com.google.common.cache.Cache;
39
import com.google.common.cache.CacheLoader.InvalidCacheLoadException;
40
import com.google.common.cache.CacheStats;
41
import com.google.common.collect.ForwardingCollection;
42
import com.google.common.collect.ForwardingConcurrentMap;
43
import com.google.common.collect.ForwardingSet;
44
import com.google.common.collect.ImmutableMap;
45
import com.google.common.collect.Iterables;
46
import com.google.common.util.concurrent.ExecutionError;
47
import com.google.common.util.concurrent.UncheckedExecutionException;
48

49
/**
50
 * A Caffeine-backed cache through a Guava facade.
51
 *
52
 * @author ben.manes@gmail.com (Ben Manes)
53
 */
54
@SuppressWarnings("serial")
55
class CaffeinatedGuavaCache<K, V> implements Cache<K, V>, Serializable {
56
  private static final long serialVersionUID = 1L;
57

58
  private final com.github.benmanes.caffeine.cache.Cache<K, V> cache;
59
  private transient @Nullable ConcurrentMap<K, V> mapView;
60

61
  CaffeinatedGuavaCache(com.github.benmanes.caffeine.cache.Cache<K, V> cache) {
1✔
62
    this.cache = requireNonNull(cache);
1✔
63
  }
1✔
64

65
  @Override
66
  public @Nullable V getIfPresent(Object key) {
67
    @SuppressWarnings("unchecked")
UNCOV
68
    var castedKey = (K) key;
×
UNCOV
69
    return cache.getIfPresent(castedKey);
×
70
  }
71

72
  @Override
73
  @SuppressWarnings({"PMD.ExceptionAsFlowControl", "PMD.PreserveStackTrace"})
74
  public V get(K key, Callable<? extends @Nullable V> valueLoader) throws ExecutionException {
UNCOV
75
    requireNonNull(valueLoader);
×
76
    try {
UNCOV
77
      return cache.get(key, k -> {
×
78
        try {
UNCOV
79
          V value = valueLoader.call();
×
UNCOV
80
          if (value == null) {
×
UNCOV
81
            throw new InvalidCacheLoadException("null value");
×
82
          }
UNCOV
83
          return value;
×
UNCOV
84
        } catch (InvalidCacheLoadException e) {
×
UNCOV
85
          throw e;
×
UNCOV
86
        } catch (RuntimeException e) {
×
UNCOV
87
          throw new UncheckedExecutionException(e);
×
UNCOV
88
        } catch (InterruptedException e) {
×
UNCOV
89
          Thread.currentThread().interrupt();
×
UNCOV
90
          throw new CacheLoaderException(e);
×
UNCOV
91
        } catch (Exception e) {
×
UNCOV
92
          throw new CacheLoaderException(e);
×
UNCOV
93
        } catch (Error e) {
×
UNCOV
94
          throw new ExecutionError(e);
×
95
        }
96
      });
UNCOV
97
    } catch (CacheLoaderException e) {
×
UNCOV
98
      throw new ExecutionException(e.getCause());
×
99
    }
100
  }
101

102
  @Override
103
  public ImmutableMap<K, V> getAllPresent(Iterable<?> keys) {
104
    @SuppressWarnings("unchecked")
105
    var castedKeys = (Iterable<? extends K>) keys;
1✔
106
    return ImmutableMap.copyOf(cache.getAllPresent(Iterables.filter(castedKeys, Objects::nonNull)));
1✔
107
  }
108

109
  @Override
110
  public void put(K key, V value) {
111
    cache.put(key, value);
1✔
112
  }
1✔
113

114
  @Override
115
  public void putAll(Map<? extends K, ? extends V> m) {
UNCOV
116
    cache.putAll(m);
×
UNCOV
117
  }
×
118

119
  @Override
120
  public void invalidate(Object key) {
121
    @SuppressWarnings("unchecked")
UNCOV
122
    var castedKey = (K) key;
×
UNCOV
123
    cache.invalidate(castedKey);
×
UNCOV
124
  }
×
125

126
  @Override
127
  public void invalidateAll(Iterable<?> keys) {
128
    @SuppressWarnings("unchecked")
129
    var castedKeys = (Iterable<? extends K>) keys;
1✔
130
    cache.invalidateAll(Iterables.filter(castedKeys, Objects::nonNull));
1✔
131
  }
1✔
132

133
  @Override
134
  public void invalidateAll() {
UNCOV
135
    cache.invalidateAll();
×
UNCOV
136
  }
×
137

138
  @Override
139
  public long size() {
UNCOV
140
    return cache.estimatedSize();
×
141
  }
142

143
  @Override
144
  public CacheStats stats() {
UNCOV
145
    var stats = cache.stats();
×
UNCOV
146
    return new CacheStats(stats.hitCount(), stats.missCount(), stats.loadSuccessCount(),
×
UNCOV
147
        stats.loadFailureCount(), stats.totalLoadTime(), stats.evictionCount());
×
148
  }
149

150
  @Override
151
  public ConcurrentMap<K, V> asMap() {
152
    return (mapView == null) ? (mapView = new AsMapView()) : mapView;
1✔
153
  }
154

155
  @Override
156
  public void cleanUp() {
UNCOV
157
    cache.cleanUp();
×
UNCOV
158
  }
×
159

160
  final class AsMapView extends ForwardingConcurrentMap<K, V> {
1✔
161
    @Nullable Set<Entry<K, V>> entrySet;
162
    @Nullable Collection<V> values;
163
    @Nullable Set<K> keySet;
164

165
    @Override public boolean containsKey(@Nullable Object key) {
166
      return (key != null) && delegate().containsKey(key);
1✔
167
    }
168
    @Override public boolean containsValue(@Nullable Object value) {
169
      return (value != null) && delegate().containsValue(value);
1✔
170
    }
171
    @Override public @Nullable V get(@Nullable Object key) {
172
      return (key == null) ? null : delegate().get(key);
1✔
173
    }
174
    @Override public @Nullable V remove(@Nullable Object key) {
175
      return (key == null) ? null : delegate().remove(key);
1✔
176
    }
177
    @Override public boolean remove(@Nullable Object key, @Nullable Object value) {
178
      return (key != null) && (value != null) && delegate().remove(key, value);
1✔
179
    }
180
    @Override public boolean replace(K key, @Nullable V oldValue, V newValue) {
181
      return (oldValue != null) && delegate().replace(key, oldValue, newValue);
1✔
182
    }
183
    @Override public void replaceAll(BiFunction<? super K, ? super V, ? extends V> function) {
184
      delegate().replaceAll(function);
1✔
185
    }
1✔
186
    @Override public V computeIfAbsent(K key, Function<? super K, ? extends V> mappingFunction) {
187
      return delegate().computeIfAbsent(key, mappingFunction);
1✔
188
    }
189
    @Override public @Nullable V computeIfPresent(K key,
190
        BiFunction<? super K, ? super V, ? extends @Nullable V> remappingFunction) {
191
      return delegate().computeIfPresent(key, remappingFunction);
1✔
192
    }
193
    @Override public V compute(K key,
194
        BiFunction<? super K, ? super V, ? extends V> remappingFunction) {
195
      return delegate().compute(key, remappingFunction);
1✔
196
    }
197
    @Override public V merge(K key, V value,
198
        BiFunction<? super V, ? super V, ? extends V> remappingFunction) {
199
      return delegate().merge(key, value, remappingFunction);
1✔
200
    }
201
    @Override public void forEach(BiConsumer<? super K, ? super V> action) {
202
      delegate().forEach(action);
1✔
203
    }
1✔
204
    @Override public Set<K> keySet() {
205
      return (keySet == null) ? (keySet = new KeySetView()) : keySet;
1✔
206
    }
207
    @Override public Collection<V> values() {
208
      return (values == null) ? (values = new ValuesView()) : values;
1!
209
    }
210
    @Override public Set<Entry<K, V>> entrySet() {
211
      return (entrySet == null) ? (entrySet = new EntrySetView()) : entrySet;
1✔
212
    }
213
    @Override protected ConcurrentMap<K, V> delegate() {
214
      return cache.asMap();
1✔
215
    }
216
  }
217

218
  final class KeySetView extends ForwardingSet<K> {
1✔
219
    @Override public boolean contains(@Nullable Object o) {
220
      return (o != null) && delegate().contains(o);
1✔
221
    }
222
    @Override public boolean remove(@Nullable Object o) {
223
      return (o != null) && delegate().remove(o);
1✔
224
    }
225
    @Override public boolean removeIf(Predicate<? super K> filter) {
226
      return delegate().removeIf(filter);
1✔
227
    }
228
    @Override public void forEach(Consumer<? super K> action) {
229
      delegate().forEach(action);
1✔
230
    }
1✔
231
    @Override public Spliterator<K> spliterator() {
232
      return delegate().spliterator();
1✔
233
    }
234
    @Override protected Set<K> delegate() {
235
      return cache.asMap().keySet();
1✔
236
    }
237
  }
238

239
  final class ValuesView extends ForwardingCollection<V> {
1✔
240
    @Override public boolean contains(@Nullable Object o) {
241
      return (o != null) && delegate().contains(o);
1✔
242
    }
243
    @Override public boolean remove(@Nullable Object o) {
244
      return (o != null) && delegate().remove(o);
1✔
245
    }
246
    @Override public boolean removeIf(Predicate<? super V> filter) {
247
      return delegate().removeIf(filter);
1✔
248
    }
249
    @Override public void forEach(Consumer<? super V> action) {
250
      delegate().forEach(action);
1✔
251
    }
1✔
252
    @Override public Spliterator<V> spliterator() {
253
      return delegate().spliterator();
1✔
254
    }
255
    @Override protected Collection<V> delegate() {
256
      return cache.asMap().values();
1✔
257
    }
258
  }
259

260
  @SuppressWarnings("NullableProblems")
261
  final class EntrySetView extends ForwardingSet<Entry<K, V>> {
1✔
262
    @Override public boolean add(Entry<K, V> entry) {
263
      throw new UnsupportedOperationException();
1✔
264
    }
265
    @Override public boolean addAll(Collection<? extends Entry<K, V>> entries) {
266
      if (entries.isEmpty()) {
1✔
267
        return false;
1✔
268
      }
269
      throw new UnsupportedOperationException();
1✔
270
    }
271
    @Override public boolean removeIf(Predicate<? super Entry<K, V>> filter) {
272
      return delegate().removeIf(filter);
1✔
273
    }
274
    @Override public void forEach(Consumer<? super Entry<K, V>> action) {
275
      delegate().forEach(action);
1✔
276
    }
1✔
277
    @Override public Spliterator<Entry<K, V>> spliterator() {
278
      return delegate().spliterator();
1✔
279
    }
280
    @Override protected Set<Entry<K, V>> delegate() {
281
      return cache.asMap().entrySet();
1✔
282
    }
283
  }
284

285
  static final class CacheLoaderException extends RuntimeException {
286
    private static final long serialVersionUID = 1L;
287

288
    CacheLoaderException(Throwable cause) {
289
      super(null, cause, /* enableSuppression= */ false, /* writableStackTrace= */ false);
1✔
290
    }
1✔
291
  }
292
}
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