• 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/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.Optional;
23
import java.util.Set;
24
import java.util.concurrent.Executor;
25

26
import javax.cache.Cache;
27
import javax.cache.CacheException;
28
import javax.cache.expiry.ExpiryPolicy;
29
import javax.cache.integration.CacheLoader;
30

31
import org.jspecify.annotations.Nullable;
32

33
import com.github.benmanes.caffeine.cache.LoadingCache;
34
import com.github.benmanes.caffeine.cache.Ticker;
35
import com.github.benmanes.caffeine.jcache.configuration.CaffeineConfiguration;
36
import com.github.benmanes.caffeine.jcache.event.EventDispatcher;
37
import com.github.benmanes.caffeine.jcache.management.JCacheStatisticsMXBean;
38
import com.google.errorprone.annotations.Var;
39

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

49
  @SuppressWarnings({"PMD.ExcessiveParameterList", "TooManyParameters"})
50
  public LoadingCacheProxy(String name, Executor executor, CacheManagerImpl cacheManager,
51
      CaffeineConfiguration<K, V> configuration, LoadingCache<K, @Nullable Expirable<V>> cache,
52
      EventDispatcher<K, V> dispatcher, CacheLoader<K, V> cacheLoader, ExpiryPolicy expiry,
53
      Ticker ticker, JCacheStatisticsMXBean statistics) {
UNCOV
54
    super(name, executor, cacheManager, configuration, cache, dispatcher,
×
UNCOV
55
        Optional.of(cacheLoader), expiry, ticker, statistics);
×
UNCOV
56
    this.cache = cache;
×
UNCOV
57
  }
×
58

59
  @Override
60
  public @Nullable V get(K key) {
UNCOV
61
    requireNotClosed();
×
62
    try {
UNCOV
63
      return getOrLoad(key);
×
UNCOV
64
    } catch (NullPointerException | IllegalStateException | ClassCastException | CacheException e) {
×
UNCOV
65
      throw e;
×
UNCOV
66
    } catch (RuntimeException e) {
×
UNCOV
67
      throw new CacheException(e);
×
68
    } finally {
UNCOV
69
      dispatcher.awaitSynchronous();
×
70
    }
71
  }
72

73
  /** Retrieves the value from the cache, loading it if necessary. */
74
  private @Nullable V getOrLoad(K key) {
UNCOV
75
    boolean statsEnabled = statistics.isEnabled();
×
UNCOV
76
    long start = statsEnabled ? ticker.read() : 0L;
×
77

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

UNCOV
96
    if (expirable == null) {
×
UNCOV
97
      expirable = cache.get(key);
×
UNCOV
98
      statistics.recordMisses(1L);
×
99
    } else {
UNCOV
100
      setAccessExpireTime(key, expirable, millis);
×
UNCOV
101
      statistics.recordHits(1L);
×
102
    }
103

UNCOV
104
    @Var V value = null;
×
UNCOV
105
    if (expirable != null) {
×
UNCOV
106
      value = copyValue(expirable);
×
107
    }
UNCOV
108
    if (statsEnabled) {
×
UNCOV
109
      statistics.recordGetTime(ticker.read() - start);
×
110
    }
UNCOV
111
    return value;
×
112
  }
113

114
  @Override
115
  public Map<K, V> getAll(Set<? extends K> keys) {
UNCOV
116
    requireNotClosed();
×
UNCOV
117
    boolean statsEnabled = statistics.isEnabled();
×
UNCOV
118
    long start = statsEnabled ? ticker.read() : 0L;
×
119
    try {
UNCOV
120
      Map<K, Expirable<V>> entries = getAndFilterExpiredEntries(keys);
×
121

UNCOV
122
      if (entries.size() != keys.size()) {
×
UNCOV
123
        List<K> keysToLoad = keys.stream()
×
UNCOV
124
            .filter(key -> !entries.containsKey(key))
×
UNCOV
125
            .collect(toUnmodifiableList());
×
UNCOV
126
        entries.putAll(cache.getAll(keysToLoad));
×
127
      }
128

UNCOV
129
      Map<K, V> result = copyMap(entries);
×
UNCOV
130
      if (statsEnabled) {
×
UNCOV
131
        statistics.recordGetTime(ticker.read() - start);
×
132
      }
UNCOV
133
      return result;
×
UNCOV
134
    } catch (NullPointerException | IllegalStateException | ClassCastException | CacheException e) {
×
UNCOV
135
      throw e;
×
UNCOV
136
    } catch (RuntimeException e) {
×
UNCOV
137
      throw new CacheException(e);
×
138
    } finally {
UNCOV
139
      dispatcher.awaitSynchronous();
×
140
    }
141
  }
142
}
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