• Home
  • Features
  • Pricing
  • Docs
  • Announcements
  • Sign In

ben-manes / caffeine / #5573

03 Jul 2026 08:24PM UTC coverage: 98.975% (-1.0%) from 99.988%
#5573

push

github

ben-manes
Bound per-cycle expiration to amortize idle-recovery spikes

Cap expireAfterAccessEntries/expireAfterWriteEntries at
EXPIRATION_THRESHOLD evictions per maintenance cycle, then re-arm
via PROCESSING_TO_REQUIRED so the backlog drains across cycles.
Mirrors drainWriteBuffer's cap; keeps a large expired population
from stalling a writer-assist under evictionLock. The validation
subject pumps cleanUp until IDLE to drain the re-armed backlog.

3996 of 4072 branches covered (98.13%)

16 of 16 new or added lines in 1 file covered. (100.0%)

84 existing lines in 13 files now uncovered.

8205 of 8290 relevant lines covered (98.97%)

0.99 hits per line

Source File
Press 'n' to go to next uncovered line, 'b' for previous

96.49
/caffeine/src/main/java/com/github/benmanes/caffeine/cache/NodeFactory.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.cache;
17

18
import java.lang.invoke.MethodHandles;
19
import java.lang.invoke.MethodType;
20
import java.lang.ref.ReferenceQueue;
21
import java.util.concurrent.ConcurrentHashMap;
22
import java.util.concurrent.ConcurrentMap;
23

24
import org.jspecify.annotations.Nullable;
25

26
import com.github.benmanes.caffeine.cache.References.LookupKeyReference;
27
import com.github.benmanes.caffeine.cache.References.WeakKeyReference;
28
import com.google.errorprone.annotations.Var;
29

30
/**
31
 * A factory for cache nodes optimized for a particular configuration.
32
 *
33
 * @author ben.manes@gmail.com (Ben Manes)
34
 */
35
@FunctionalInterface
36
interface NodeFactory<K, V> {
37
  MethodHandles.Lookup LOOKUP = MethodHandles.lookup();
1✔
38
  MethodType FACTORY = MethodType.methodType(void.class);
1✔
39
  ConcurrentMap<String, NodeFactory<Object, Object>> FACTORIES = new ConcurrentHashMap<>();
1✔
40

41
  RetiredStrongKey RETIRED_STRONG_KEY = new RetiredStrongKey();
1✔
42
  RetiredWeakKey RETIRED_WEAK_KEY = new RetiredWeakKey();
1✔
43
  DeadStrongKey DEAD_STRONG_KEY = new DeadStrongKey();
1✔
44
  DeadWeakKey DEAD_WEAK_KEY = new DeadWeakKey();
1✔
45
  String ACCESS_TIME = "accessTime";
46
  String WRITE_TIME = "writeTime";
47
  String VALUE = "value";
48
  String KEY = "key";
49

50
  /** Returns whether this factory supports weak values. */
51
  default boolean weakValues() {
52
    return false;
1✔
53
  }
54

55
  /** Returns whether this factory supports soft values. */
56
  default boolean softValues() {
57
    return false;
1✔
58
  }
59

60
  /** Returns a node optimized for the specified features. */
61
  Node<K, V> newNode(Object keyReference, V value,
62
      @Nullable ReferenceQueue<V> valueReferenceQueue, int weight, long now);
63

64
  /**
65
   * Returns a key suitable for inserting into the cache. If the cache holds keys strongly then the
66
   * key is returned. If the cache holds keys weakly then a {@link java.lang.ref.Reference<K>}
67
   * holding the key argument is returned.
68
   */
69
  default Object newReferenceKey(K key, ReferenceQueue<K> referenceQueue) {
70
    return key;
1✔
71
  }
72

73
  /**
74
   * Returns a key suitable for looking up an entry in the cache. If the cache holds keys strongly
75
   * then the key is returned. If the cache holds keys weakly then a {@link LookupKeyReference}
76
   * holding the key argument is returned.
77
   */
78
  default Object newLookupKey(Object key) {
79
    return key;
1✔
80
  }
81

82
  /** Returns a factory optimized for the specified features. */
83
  @SuppressWarnings("unchecked")
84
  static <K, V> NodeFactory<K, V> newFactory(Caffeine<K, V> builder, boolean isAsync) {
85
    if (builder.interner) {
1✔
86
      return (NodeFactory<K, V>) Interned.FACTORY;
1✔
87
    }
88
    var className = getClassName(builder, isAsync);
1✔
89
    return loadFactory(className);
1✔
90
  }
91

92
  static String getClassName(Caffeine<?, ?> builder, boolean isAsync) {
93
    var className = new StringBuilder();
1✔
94
    if (builder.isStrongKeys()) {
1✔
95
      className.append('P');
1✔
96
    } else {
97
      className.append('F');
1✔
98
    }
99
    if (builder.isStrongValues()) {
1✔
100
      className.append('S');
1✔
101
    } else if (builder.isWeakValues()) {
1✔
102
      className.append('W');
1✔
103
    } else {
104
      className.append('D');
1✔
105
    }
106
    if (builder.expiresVariable()) {
1✔
107
      if (builder.refreshAfterWrite()) {
1✔
108
        className.append('A');
1✔
109
        if (builder.evicts()) {
1✔
110
          className.append('W');
1✔
111
        }
112
      } else {
113
        className.append('W');
1✔
114
      }
115
    } else {
116
      if (builder.expiresAfterAccess()) {
1✔
117
        className.append('A');
1✔
118
      }
119
      if (builder.expiresAfterWrite()) {
1✔
120
        className.append('W');
1✔
121
      }
122
    }
123
    if (builder.refreshAfterWrite()) {
1✔
124
      className.append('R');
1✔
125
    }
126
    if (builder.evicts()) {
1✔
127
      className.append('M');
1✔
128
      if (isAsync || (builder.isWeighted() && (builder.weigher != Weigher.singletonWeigher()))) {
1✔
129
        className.append('W');
1✔
130
      } else {
131
        className.append('S');
1✔
132
      }
133
    }
134
    return className.toString();
1✔
135
  }
136

137
  @SuppressWarnings("unchecked")
138
  static <K, V> NodeFactory<K, V> loadFactory(String className) {
139
    @Var var factory = FACTORIES.get(className);
1✔
140
    if (factory == null) {
1✔
141
      factory = FACTORIES.computeIfAbsent(className, NodeFactory::newFactory);
1✔
142
    }
143
    return (NodeFactory<K, V>) factory;
1✔
144
  }
145

146
  @SuppressWarnings("unchecked")
147
  static NodeFactory<Object, Object> newFactory(String className) {
148
    try {
149
      var clazz = LOOKUP.findClass(Node.class.getPackageName() + "." + className);
1✔
150
      var constructor = LOOKUP.findConstructor(clazz, FACTORY);
1✔
151
      return (NodeFactory<Object, Object>) constructor.invoke();
1✔
152
    } catch (RuntimeException | Error e) {
1✔
153
      throw e;
1✔
UNCOV
154
    } catch (Throwable t) {
×
UNCOV
155
      throw new IllegalStateException(className, t);
×
156
    }
157
  }
158

159
  final class RetiredWeakKey extends WeakKeyReference<Object> {
160
    RetiredWeakKey() { super(/* key= */ null, /* queue= */ null); }
1✔
161
  }
162
  final class DeadWeakKey extends WeakKeyReference<Object> {
163
    DeadWeakKey() { super(/* key= */ null, /* queue= */ null); }
1✔
164
  }
165
  final class RetiredStrongKey {}
1✔
166
  final class DeadStrongKey {}
1✔
167
}
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