• 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

95.92
/caffeine/src/main/java/com/github/benmanes/caffeine/cache/Interner.java
1
/*
2
 * Copyright 2022 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.cache;
17

18
import java.lang.ref.Reference;
19
import java.lang.ref.ReferenceQueue;
20
import java.util.Objects;
21
import java.util.concurrent.ConcurrentHashMap;
22
import java.util.concurrent.ConcurrentMap;
23

24
import org.jspecify.annotations.NullMarked;
25
import org.jspecify.annotations.Nullable;
26

27
import com.github.benmanes.caffeine.cache.References.LookupKeyEqualsReference;
28
import com.github.benmanes.caffeine.cache.References.WeakKeyEqualsReference;
29

30
/**
31
 * Provides similar behavior to {@link String#intern} for any immutable type.
32
 * <p>
33
 * Note that {@code String.intern()} has some well-known performance limitations and should
34
 * generally be avoided. Prefer {@link Interner#newWeakInterner} or another {@code Interner}
35
 * implementation even for {@code String} interning.
36
 *
37
 * @param <E> the type of elements
38
 * @author ben.manes@gmail.com (Ben Manes)
39
 */
40
@NullMarked
41
@FunctionalInterface
42
public interface Interner<E> {
43

44
  /**
45
   * Chooses and returns the representative instance for any collection of instances that are
46
   * equal to each other. If two {@linkplain Object#equals equal} inputs are given to this method,
47
   * both calls will return the same instance. That is, {@code intern(a).equals(a)} always holds,
48
   * and {@code intern(a) == intern(b)} if and only if {@code a.equals(b)}. Note that {@code
49
   * intern(a)} is permitted to return one instance now and a different instance later if the
50
   * original interned instance was garbage-collected.
51
   * <p>
52
   * <b>Warning:</b> Do not use with mutable objects.
53
   *
54
   * @param sample the element to add if absent
55
   * @return the representative instance, possibly the {@code sample} if absent
56
   * @throws NullPointerException if {@code sample} is null
57
   */
58
  E intern(E sample);
59

60
  /**
61
   * Returns a new thread-safe interner that retains a strong reference to each instance it has
62
   * interned, thus preventing these instances from being garbage-collected.
63
   *
64
   * @param <E> the type of elements
65
   * @return an interner for retrieving the canonical instance
66
   */
67
  static <E> Interner<E> newStrongInterner() {
68
    return new StrongInterner<>();
1✔
69
  }
70

71
  /**
72
   * Returns a new thread-safe interner that retains a weak reference to each instance it has
73
   * interned, and so does not prevent these instances from being garbage-collected.
74
   *
75
   * @param <E> the type of elements
76
   * @return an interner for retrieving the canonical instance
77
   */
78
  static <E> Interner<E> newWeakInterner() {
79
    return new WeakInterner<>();
1✔
80
  }
81
}
82

83
final class StrongInterner<E> implements Interner<E> {
84
  final ConcurrentMap<E, E> map;
85

86
  StrongInterner() {
1✔
87
    map = new ConcurrentHashMap<>();
1✔
88
  }
1✔
89
  @Override public E intern(E sample) {
90
    E canonical = map.get(sample);
1✔
91
    if (canonical != null) {
1!
UNCOV
92
      return canonical;
×
93
    }
94

95
    @Nullable E value = map.putIfAbsent(sample, sample);
1✔
96
    return (value == null) ? sample : value;
1!
97
  }
98
}
99

100
final class WeakInterner<E> implements Interner<E> {
101
  final BoundedLocalCache<E, Boolean> cache;
102

103
  WeakInterner() {
1✔
104
    cache = Caffeine.newWeakInterner();
1✔
105
  }
1✔
106
  @Override public E intern(E sample) {
107
    for (;;) {
108
      E canonical = cache.getKey(sample);
1✔
109
      if (canonical != null) {
1✔
110
        return canonical;
1✔
111
      }
112

113
      var value = cache.putIfAbsent(sample, true);
1✔
114
      if (value == null) {
1✔
115
        return sample;
1✔
116
      }
117
    }
1✔
118
  }
119
}
120

121
@SuppressWarnings({"BooleanLiteral", "unchecked"})
122
final class Interned<K, V> extends Node<K, V> implements NodeFactory<K, V> {
123
  static final NodeFactory<Object, Object> FACTORY = new Interned<>();
1✔
124

125
  volatile Reference<?> keyReference;
126

127
  Interned() {
1✔
128
    this.keyReference = NodeFactory.DEAD_WEAK_KEY;
1✔
129
  }
1✔
130
  Interned(Reference<K> keyReference) {
1✔
131
    this.keyReference = keyReference;
1✔
132
  }
1✔
133
  @Override public @Nullable K getKey() {
134
    return (K) keyReference.get();
1✔
135
  }
136
  @Override public Object getKeyReference() {
137
    return keyReference;
1✔
138
  }
139
  @Override public Object getKeyReferenceOrNull() {
140
    return keyReference;
1✔
141
  }
142
  @Override public V getValue() {
143
    return (V) Boolean.TRUE;
1✔
144
  }
145
  @Override public V getValueReference() {
146
    return (V) Boolean.TRUE;
1✔
147
  }
UNCOV
148
  @Override public void setValue(V value, @Nullable ReferenceQueue<V> referenceQueue) {}
×
149
  @Override public boolean containsValue(Object value) {
150
    return Objects.equals(value, getValue());
1✔
151
  }
152
  @Override public Node<K, V> newNode(Object keyReference, V value,
153
      @Nullable ReferenceQueue<V> valueReferenceQueue, int weight, long now) {
154
    return new Interned<>((Reference<K>) keyReference);
1✔
155
  }
156
  @Override public Object newLookupKey(Object key) {
157
    return new LookupKeyEqualsReference<>(key);
1✔
158
  }
159
  @Override public Object newReferenceKey(K key, ReferenceQueue<K> referenceQueue) {
160
    return new WeakKeyEqualsReference<>(key, referenceQueue);
1✔
161
  }
162
  @Override public boolean isAlive() {
163
    Object keyRef = keyReference;
1✔
164
    return (keyRef != RETIRED_WEAK_KEY) && (keyRef != DEAD_WEAK_KEY);
1✔
165
  }
166
  @Override public boolean isRetired() {
167
    return (keyReference == RETIRED_WEAK_KEY);
1✔
168
  }
169
  @Override public void retire() {
170
    var keyRef = keyReference;
1✔
171
    keyReference = RETIRED_WEAK_KEY;
1✔
172
    keyRef.clear();
1✔
173
  }
1✔
174
  @Override public boolean isDead() {
175
    return (keyReference == DEAD_WEAK_KEY);
1✔
176
  }
177
  @Override public void die() {
178
    var keyRef = keyReference;
1✔
179
    keyReference = DEAD_WEAK_KEY;
1✔
180
    keyRef.clear();
1✔
181
  }
1✔
182
}
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