• 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

76.98
/guava/src/main/java/com/github/benmanes/caffeine/guava/CaffeinatedGuavaLoadingCache.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.HashMap;
22
import java.util.Map;
23
import java.util.Set;
24
import java.util.concurrent.CompletableFuture;
25
import java.util.concurrent.ExecutionException;
26
import java.util.concurrent.Executor;
27

28
import org.jspecify.annotations.Nullable;
29

30
import com.github.benmanes.caffeine.cache.CacheLoader;
31
import com.google.common.cache.CacheLoader.InvalidCacheLoadException;
32
import com.google.common.cache.CacheLoader.UnsupportedLoadingOperationException;
33
import com.google.common.cache.LoadingCache;
34
import com.google.common.collect.ImmutableList;
35
import com.google.common.collect.ImmutableMap;
36
import com.google.common.util.concurrent.ExecutionError;
37
import com.google.common.util.concurrent.FutureCallback;
38
import com.google.common.util.concurrent.Futures;
39
import com.google.common.util.concurrent.ListenableFuture;
40
import com.google.common.util.concurrent.UncheckedExecutionException;
41

42
/**
43
 * A Caffeine-backed loading cache through a Guava facade.
44
 *
45
 * @author ben.manes@gmail.com (Ben Manes)
46
 */
47
@SuppressWarnings("serial")
48
final class CaffeinatedGuavaLoadingCache<K, V>
49
    extends CaffeinatedGuavaCache<K, V> implements LoadingCache<K, V> {
50
  private static final ThreadLocal<Boolean> nullBulkLoad = new ThreadLocal<>();
1✔
51
  private static final long serialVersionUID = 1L;
52

53
  private final com.github.benmanes.caffeine.cache.LoadingCache<K, V> cache;
54

55
  CaffeinatedGuavaLoadingCache(com.github.benmanes.caffeine.cache.LoadingCache<K, V> cache) {
56
    super(cache);
1✔
57
    this.cache = cache;
1✔
58
  }
1✔
59

60
  @Override
61
  @SuppressWarnings("PMD.PreserveStackTrace")
62
  public V get(K key) throws ExecutionException {
UNCOV
63
    requireNonNull(key);
×
64
    try {
UNCOV
65
      return cache.get(key);
×
UNCOV
66
    } catch (InvalidCacheLoadException e) {
×
UNCOV
67
      throw e;
×
UNCOV
68
    } catch (CacheLoaderException e) {
×
UNCOV
69
      throw new ExecutionException(e.getCause());
×
UNCOV
70
    } catch (RuntimeException e) {
×
UNCOV
71
      throw new UncheckedExecutionException(e);
×
UNCOV
72
    } catch (Error e) {
×
UNCOV
73
      throw new ExecutionError(e);
×
74
    }
75
  }
76

77
  @Override
78
  @SuppressWarnings({"CatchingUnchecked", "PMD.PreserveStackTrace"})
79
  public V getUnchecked(K key) {
80
    requireNonNull(key);
1✔
81
    try {
82
      return cache.get(key);
1✔
UNCOV
83
    } catch (InvalidCacheLoadException e) {
×
UNCOV
84
      throw e;
×
85
    } catch (CacheLoaderException e) {
1✔
86
      throw new UncheckedExecutionException(e.getCause());
1✔
87
    } catch (Exception e) {
1✔
88
      throw new UncheckedExecutionException(e);
1✔
UNCOV
89
    } catch (Error e) {
×
UNCOV
90
      throw new ExecutionError(e);
×
91
    }
92
  }
93

94
  @Override
95
  @SuppressWarnings({"CatchingUnchecked", "PMD.PreserveStackTrace"})
96
  public ImmutableMap<K, V> getAll(Iterable<? extends K> keys) throws ExecutionException {
97
    requireNonNull(keys);
1✔
98
    var keysToLoad = ImmutableList.copyOf(keys);
1✔
99
    try {
100
      Map<K, V> result = cache.getAll(keysToLoad);
1✔
101
      if (nullBulkLoad.get() != null) {
1✔
102
        throw new InvalidCacheLoadException("null key or value");
1✔
103
      }
104
      for (K key : keysToLoad) {
1✔
105
        if (!result.containsKey(key)) {
1!
UNCOV
106
          throw new InvalidCacheLoadException("loadAll failed to return a value for " + key);
×
107
        }
108
      }
1✔
109
      return ImmutableMap.copyOf(result);
1✔
110
    } catch (InvalidCacheLoadException e) {
1✔
111
      throw e;
1✔
UNCOV
112
    } catch (CacheLoaderException e) {
×
UNCOV
113
      throw new ExecutionException(e.getCause());
×
114
    } catch (Exception e) {
1✔
115
      throw new UncheckedExecutionException(e);
1✔
UNCOV
116
    } catch (Error e) {
×
UNCOV
117
      throw new ExecutionError(e);
×
118
    } finally {
119
      nullBulkLoad.remove();
1✔
120
    }
121
  }
122

123
  @Override
124
  @SuppressWarnings("deprecation")
125
  public V apply(K key) {
UNCOV
126
    return getUnchecked(key);
×
127
  }
128

129
  @Override
130
  @SuppressWarnings("FutureReturnValueIgnored")
131
  public void refresh(K key) {
132
    cache.refresh(key);
1✔
133
  }
1✔
134

135
  abstract static class CaffeinatedLoader<K, V> implements CacheLoader<K, V>, Serializable {
136
    private static final long serialVersionUID = 1L;
137

138
    final com.google.common.cache.CacheLoader<K, V> cacheLoader;
139

140
    CaffeinatedLoader(com.google.common.cache.CacheLoader<K, V> cacheLoader) {
1✔
141
      this.cacheLoader = requireNonNull(cacheLoader);
1✔
142
    }
1✔
143
    @SuppressWarnings("ConstantValue")
144
    @Override public CompletableFuture<V> asyncReload(K key, V oldValue, Executor executor) {
145
      var future = new CompletableFuture<V>();
1✔
146
      try {
147
        ListenableFuture<V> reloader = cacheLoader.reload(key, oldValue);
1✔
148
        if (reloader == null) {
1✔
149
          future.completeExceptionally(new InvalidCacheLoadException("null future"));
1✔
150
        } else {
151
          Futures.addCallback(reloader, new FutureCompleter<>(future), Runnable::run);
1✔
152
        }
153
      } catch (Throwable t) {
1✔
154
        future.completeExceptionally(t);
1✔
155
      }
1✔
156
      return future;
1✔
157
    }
158
    /** Sequentially loads each missing entry. */
159
    @SuppressWarnings("PMD.SignatureDeclareThrowsException")
160
    Map<K, V> loadSequentially(Set<? extends K> keys) throws Exception {
161
      var result = new HashMap<K, V>(keys.size(), /* loadFactor= */ 1.0f);
1✔
162
      for (K key : keys) {
1✔
163
        result.put(key, load(key));
1✔
164
      }
1✔
165
      return result;
1✔
166
    }
167
  }
168

169
  static class InternalSingleLoader<K, V> extends CaffeinatedLoader<K, V> {
170
    private static final long serialVersionUID = 1L;
171

172
    InternalSingleLoader(com.google.common.cache.CacheLoader<K, V> cacheLoader) {
173
      super(cacheLoader);
1✔
174
    }
1✔
175
    @SuppressWarnings("ConstantValue")
176
    @Override public V load(K key) {
177
      try {
178
        V value = cacheLoader.load(key);
1✔
179
        if (value == null) {
1!
UNCOV
180
          throw new InvalidCacheLoadException("null value");
×
181
        }
182
        return value;
1✔
183
      } catch (RuntimeException | Error e) {
1✔
184
        throw e;
1✔
UNCOV
185
      } catch (InterruptedException e) {
×
UNCOV
186
        Thread.currentThread().interrupt();
×
UNCOV
187
        throw new CacheLoaderException(e);
×
188
      } catch (Exception e) {
1✔
189
        throw new CacheLoaderException(e);
1✔
190
      }
191
    }
192
  }
193

194
  static final class InternalBulkLoader<K, V> extends InternalSingleLoader<K, V> {
195
    private static final long serialVersionUID = 1L;
196

197
    InternalBulkLoader(com.google.common.cache.CacheLoader<K, V> cacheLoader) {
198
      super(cacheLoader);
1✔
199
    }
1✔
200
    @SuppressWarnings("ConstantValue")
201
    @Override public Map<K, V> loadAll(Set<? extends K> keys) {
202
      try {
203
        Map<K, V> loaded;
204
        try {
205
          loaded = cacheLoader.loadAll(keys);
1✔
206
        } catch (UnsupportedLoadingOperationException e) {
1✔
207
          return loadSequentially(keys);
1✔
208
        }
1✔
209
        if (loaded == null) {
1✔
210
          throw new InvalidCacheLoadException("null map");
1✔
211
        }
212
        var result = new HashMap<K, V>(loaded.size(), /* loadFactor= */ 1.0f);
1✔
213
        loaded.forEach((key, value) -> {
1✔
214
          if ((key == null) || (value == null)) {
1✔
215
            nullBulkLoad.set(true);
1✔
216
          } else {
217
            result.put(key, value);
1✔
218
          }
219
        });
1✔
220
        return result;
1✔
221
      } catch (RuntimeException | Error e) {
1✔
222
        throw e;
1✔
UNCOV
223
      } catch (InterruptedException e) {
×
UNCOV
224
        Thread.currentThread().interrupt();
×
UNCOV
225
        throw new CacheLoaderException(e);
×
UNCOV
226
      } catch (Exception e) {
×
UNCOV
227
        throw new CacheLoaderException(e);
×
228
      }
229
    }
230
  }
231

232
  static class ExternalSingleLoader<K, V> extends CaffeinatedLoader<K, V> {
233
    private static final long serialVersionUID = 1L;
234

235
    ExternalSingleLoader(com.google.common.cache.CacheLoader<K, V> cacheLoader) {
236
      super(cacheLoader);
1✔
237
    }
1✔
238
    @SuppressWarnings("ConstantValue")
239
    @Override public V load(K key) throws Exception {
240
      V value = cacheLoader.load(key);
1✔
241
      if (value == null) {
1✔
242
        throw new InvalidCacheLoadException("null value");
1✔
243
      }
244
      return value;
1✔
245
    }
246
  }
247

248
  static final class ExternalBulkLoader<K, V> extends ExternalSingleLoader<K, V> {
249
    private static final long serialVersionUID = 1L;
250

251
    ExternalBulkLoader(com.google.common.cache.CacheLoader<K, V> cacheLoader) {
252
      super(cacheLoader);
1✔
253
    }
1✔
254
    @SuppressWarnings("ConstantValue")
255
    @Override public Map<K, V> loadAll(Set<? extends K> keys) throws Exception {
256
      Map<K, V> loaded;
257
      try {
258
        loaded = cacheLoader.loadAll(keys);
1✔
259
      } catch (UnsupportedLoadingOperationException e) {
1✔
260
        return loadSequentially(keys);
1✔
261
      }
1✔
262
      if (loaded == null) {
1✔
263
        throw new InvalidCacheLoadException("null map");
1✔
264
      }
265
      for (var value : loaded.values()) {
1✔
266
        if (value == null) {
1✔
267
          throw new InvalidCacheLoadException("null value");
1✔
268
        }
269
      }
1✔
270
      return loaded;
1✔
271
    }
272
  }
273

274
  static final class FutureCompleter<V> implements FutureCallback<V> {
275
    final CompletableFuture<V> future;
276

277
    FutureCompleter(CompletableFuture<V> future) {
1✔
278
      this.future = future;
1✔
279
    }
1✔
280
    @Override public void onSuccess(@Nullable V value) {
281
      if (value == null) {
1✔
282
        future.completeExceptionally(new InvalidCacheLoadException("null value"));
1✔
283
      } else {
284
        future.complete(value);
1✔
285
      }
286
    }
1✔
287
    @Override public void onFailure(Throwable t) {
288
      future.completeExceptionally(t);
1✔
289
    }
1✔
290
  }
291
}
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