• 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

72.92
/caffeine/src/main/java/com/github/benmanes/caffeine/cache/Scheduler.java
1
/*
2
 * Copyright 2019 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 static java.util.Objects.requireNonNull;
19

20
import java.io.Serializable;
21
import java.lang.System.Logger;
22
import java.lang.System.Logger.Level;
23
import java.util.concurrent.CompletableFuture;
24
import java.util.concurrent.Executor;
25
import java.util.concurrent.Future;
26
import java.util.concurrent.ScheduledExecutorService;
27
import java.util.concurrent.TimeUnit;
28

29
import org.jspecify.annotations.NullMarked;
30
import org.jspecify.annotations.Nullable;
31

32
/**
33
 * A scheduler that submits a task to an executor after a given delay.
34
 *
35
 * @author ben.manes@gmail.com (Ben Manes)
36
 */
37
@NullMarked
38
@FunctionalInterface
39
public interface Scheduler {
40

41
  /**
42
   * Returns a future that will submit the task to the executor after the given delay.
43
   *
44
   * @param executor the executor to run the task
45
   * @param command the runnable task to schedule
46
   * @param delay how long to delay, in units of {@code unit}
47
   * @param unit a {@code TimeUnit} determining how to interpret the {@code delay} parameter
48
   * @return a scheduled future representing the pending submission of the task
49
   */
50
  Future<? extends @Nullable Object> schedule(
51
      Executor executor, Runnable command, long delay, TimeUnit unit);
52

53
  /**
54
   * Returns a scheduler that always returns a successfully completed future.
55
   *
56
   * @return a scheduler that always returns a successfully completed future
57
   */
58
  static Scheduler disabledScheduler() {
59
    return DisabledScheduler.INSTANCE;
1✔
60
  }
61

62
  /**
63
   * Returns a scheduler that uses the system-wide scheduling thread by using
64
   * {@link CompletableFuture#delayedExecutor}.
65
   *
66
   * @return a scheduler that uses the system-wide scheduling thread
67
   */
68
  static Scheduler systemScheduler() {
69
    return SystemScheduler.INSTANCE;
1✔
70
  }
71

72
  /**
73
   * Returns a scheduler that delegates to the a {@link ScheduledExecutorService}.
74
   * <p>
75
   * Note that this implementation will ignore scheduling the task if the executor was shutdown or
76
   * the submission was rejected. Consider implementing your own adapter if different behavior is
77
   * required.
78
   *
79
   * @param scheduledExecutorService the executor to schedule on
80
   * @return a scheduler that delegates to the a {@link ScheduledExecutorService}
81
   */
82
  static Scheduler forScheduledExecutorService(ScheduledExecutorService scheduledExecutorService) {
83
    return new ExecutorServiceScheduler(scheduledExecutorService);
1✔
84
  }
85

86
  /**
87
   * Returns a scheduler that suppresses and logs any exception thrown by the delegate
88
   * {@code scheduler}.
89
   *
90
   * @param scheduler the scheduler to delegate to
91
   * @return a scheduler that suppresses and logs any exception thrown by the delegate
92
   */
93
  static Scheduler guardedScheduler(Scheduler scheduler) {
94
    return (scheduler instanceof GuardedScheduler) ? scheduler : new GuardedScheduler(scheduler);
1✔
95
  }
96
}
97

98
enum SystemScheduler implements Scheduler {
1✔
99
  INSTANCE;
1✔
100

101
  @Override
102
  public Future<?> schedule(Executor executor, Runnable command, long delay, TimeUnit unit) {
UNCOV
103
    Executor delayedExecutor = CompletableFuture.delayedExecutor(delay, unit, executor);
×
UNCOV
104
    return CompletableFuture.runAsync(command, delayedExecutor);
×
105
  }
106
}
107

108
final class ExecutorServiceScheduler implements Scheduler, Serializable {
109
  private static final Logger logger = System.getLogger(ExecutorServiceScheduler.class.getName());
1✔
110
  private static final long serialVersionUID = 1;
111

112
  @SuppressWarnings("serial")
113
  final ScheduledExecutorService scheduledExecutorService;
114

115
  ExecutorServiceScheduler(ScheduledExecutorService scheduledExecutorService) {
1✔
116
    this.scheduledExecutorService = requireNonNull(scheduledExecutorService);
1✔
117
  }
1✔
118

119
  @Override
120
  public Future<? extends @Nullable Object> schedule(
121
      Executor executor, Runnable command, long delay, TimeUnit unit) {
122
    requireNonNull(executor);
1✔
123
    requireNonNull(command);
1✔
124
    requireNonNull(unit);
1✔
125

126
    if (scheduledExecutorService.isShutdown()) {
1!
UNCOV
127
      return DisabledFuture.instance();
×
128
    }
129
    return scheduledExecutorService.schedule(() -> {
1✔
130
      try {
131
        executor.execute(command);
1✔
UNCOV
132
      } catch (Throwable t) {
×
UNCOV
133
        logger.log(Level.WARNING, "Exception thrown when submitting scheduled task", t);
×
UNCOV
134
        throw t;
×
135
      }
1✔
136
    }, delay, unit);
1✔
137
  }
138
}
139

140
final class GuardedScheduler implements Scheduler, Serializable {
141
  private static final Logger logger = System.getLogger(GuardedScheduler.class.getName());
1✔
142
  private static final long serialVersionUID = 1;
143

144
  @SuppressWarnings("serial")
145
  final Scheduler delegate;
146

147
  GuardedScheduler(Scheduler delegate) {
1✔
148
    this.delegate = requireNonNull(delegate);
1✔
149
  }
1✔
150

151
  @Override
152
  @SuppressWarnings("ConstantValue")
153
  public Future<? extends @Nullable Object> schedule(
154
      Executor executor, Runnable command, long delay, TimeUnit unit) {
155
    try {
156
      var future = delegate.schedule(executor, command, delay, unit);
1✔
157
      return (future == null) ? DisabledFuture.instance() : future;
1!
UNCOV
158
    } catch (Throwable t) {
×
UNCOV
159
      logger.log(Level.WARNING, "Exception thrown by scheduler; discarded task", t);
×
UNCOV
160
      return DisabledFuture.instance();
×
161
    }
162
  }
163
}
164

165
enum DisabledScheduler implements Scheduler {
1✔
166
  INSTANCE;
1✔
167

168
  @Override
169
  public Future<? extends @Nullable Object> schedule(
170
      Executor executor, Runnable command, long delay, TimeUnit unit) {
171
    requireNonNull(executor);
1✔
172
    requireNonNull(command);
1✔
173
    requireNonNull(unit);
1✔
174
    return DisabledFuture.instance();
1✔
175
  }
176
}
177

178
enum DisabledFuture implements Future<@Nullable Void> {
1✔
179
  INSTANCE;
1✔
180

181
  static Future<? extends @Nullable Object> instance() {
182
    return INSTANCE;
1✔
183
  }
184

185
  @Override public boolean isDone() {
186
    return true;
1✔
187
  }
188
  @Override public boolean isCancelled() {
UNCOV
189
    return false;
×
190
  }
191
  @Override public boolean cancel(boolean mayInterruptIfRunning) {
192
    return false;
1✔
193
  }
194
  @Override public @Nullable Void get(long timeout, TimeUnit unit) {
UNCOV
195
    requireNonNull(unit);
×
UNCOV
196
    return null;
×
197
  }
198
  @Override public @Nullable Void get() {
UNCOV
199
    return null;
×
200
  }
201
}
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