Skip to content

Commit 3415b04

Browse files
committed
Use nanosecond precision for scheduling (aligned with calculations)
Closes gh-30666
1 parent a73ad52 commit 3415b04

File tree

7 files changed

+33
-43
lines changed

7 files changed

+33
-43
lines changed

spring-context/src/main/java/org/springframework/scheduling/TriggerContext.java

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/*
2-
* Copyright 2002-2022 the original author or authors.
2+
* Copyright 2002-2023 the original author or authors.
33
*
44
* Licensed under the Apache License, Version 2.0 (the "License");
55
* you may not use this file except in compliance with the License.
@@ -27,6 +27,7 @@
2727
* of a given task.
2828
*
2929
* @author Juergen Hoeller
30+
* @author Arjen Poutsma
3031
* @since 3.0
3132
*/
3233
public interface TriggerContext {
@@ -78,6 +79,7 @@ default Date lastActualExecutionTime() {
7879
/**
7980
* Return the last <i>actual</i> execution time of the task,
8081
* or {@code null} if not scheduled before.
82+
* @since 6.0
8183
*/
8284
@Nullable
8385
Instant lastActualExecution();
@@ -98,6 +100,7 @@ default Date lastCompletionTime() {
98100
/**
99101
* Return the last completion time of the task,
100102
* or {@code null} if not scheduled before.
103+
* @since 6.0
101104
*/
102105
@Nullable
103106
Instant lastCompletion();

spring-context/src/main/java/org/springframework/scheduling/concurrent/ConcurrentTaskScheduler.java

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -211,7 +211,7 @@ public ScheduledFuture<?> schedule(Runnable task, Trigger trigger) {
211211
public ScheduledFuture<?> schedule(Runnable task, Instant startTime) {
212212
Duration initialDelay = Duration.between(this.clock.instant(), startTime);
213213
try {
214-
return this.scheduledExecutor.schedule(decorateTask(task, false), initialDelay.toMillis(), TimeUnit.MILLISECONDS);
214+
return this.scheduledExecutor.schedule(decorateTask(task, false), initialDelay.toNanos(), TimeUnit.NANOSECONDS);
215215
}
216216
catch (RejectedExecutionException ex) {
217217
throw new TaskRejectedException("Executor [" + this.scheduledExecutor + "] did not accept task: " + task, ex);
@@ -222,7 +222,7 @@ public ScheduledFuture<?> schedule(Runnable task, Instant startTime) {
222222
public ScheduledFuture<?> scheduleAtFixedRate(Runnable task, Instant startTime, Duration period) {
223223
Duration initialDelay = Duration.between(this.clock.instant(), startTime);
224224
try {
225-
return this.scheduledExecutor.scheduleAtFixedRate(decorateTask(task, true), initialDelay.toMillis(), period.toMillis(), TimeUnit.MILLISECONDS);
225+
return this.scheduledExecutor.scheduleAtFixedRate(decorateTask(task, true), initialDelay.toNanos(), period.toNanos(), TimeUnit.NANOSECONDS);
226226
}
227227
catch (RejectedExecutionException ex) {
228228
throw new TaskRejectedException("Executor [" + this.scheduledExecutor + "] did not accept task: " + task, ex);
@@ -232,7 +232,7 @@ public ScheduledFuture<?> scheduleAtFixedRate(Runnable task, Instant startTime,
232232
@Override
233233
public ScheduledFuture<?> scheduleAtFixedRate(Runnable task, Duration period) {
234234
try {
235-
return this.scheduledExecutor.scheduleAtFixedRate(decorateTask(task, true), 0, period.toMillis(), TimeUnit.MILLISECONDS);
235+
return this.scheduledExecutor.scheduleAtFixedRate(decorateTask(task, true), 0, period.toNanos(), TimeUnit.NANOSECONDS);
236236
}
237237
catch (RejectedExecutionException ex) {
238238
throw new TaskRejectedException("Executor [" + this.scheduledExecutor + "] did not accept task: " + task, ex);
@@ -243,7 +243,7 @@ public ScheduledFuture<?> scheduleAtFixedRate(Runnable task, Duration period) {
243243
public ScheduledFuture<?> scheduleWithFixedDelay(Runnable task, Instant startTime, Duration delay) {
244244
Duration initialDelay = Duration.between(this.clock.instant(), startTime);
245245
try {
246-
return this.scheduledExecutor.scheduleWithFixedDelay(decorateTask(task, true), initialDelay.toMillis(), delay.toMillis(), TimeUnit.MILLISECONDS);
246+
return this.scheduledExecutor.scheduleWithFixedDelay(decorateTask(task, true), initialDelay.toNanos(), delay.toNanos(), TimeUnit.NANOSECONDS);
247247
}
248248
catch (RejectedExecutionException ex) {
249249
throw new TaskRejectedException("Executor [" + this.scheduledExecutor + "] did not accept task: " + task, ex);
@@ -253,7 +253,7 @@ public ScheduledFuture<?> scheduleWithFixedDelay(Runnable task, Instant startTim
253253
@Override
254254
public ScheduledFuture<?> scheduleWithFixedDelay(Runnable task, Duration delay) {
255255
try {
256-
return this.scheduledExecutor.scheduleWithFixedDelay(decorateTask(task, true), 0, delay.toMillis(), TimeUnit.MILLISECONDS);
256+
return this.scheduledExecutor.scheduleWithFixedDelay(decorateTask(task, true), 0, delay.toNanos(), TimeUnit.NANOSECONDS);
257257
}
258258
catch (RejectedExecutionException ex) {
259259
throw new TaskRejectedException("Executor [" + this.scheduledExecutor + "] did not accept task: " + task, ex);

spring-context/src/main/java/org/springframework/scheduling/concurrent/ReschedulingRunnable.java

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/*
2-
* Copyright 2002-2022 the original author or authors.
2+
* Copyright 2002-2023 the original author or authors.
33
*
44
* Licensed under the Apache License, Version 2.0 (the "License");
55
* you may not use this file except in compliance with the License.
@@ -80,7 +80,7 @@ public ScheduledFuture<?> schedule() {
8080
return null;
8181
}
8282
Duration initialDelay = Duration.between(this.triggerContext.getClock().instant(), this.scheduledExecutionTime);
83-
this.currentFuture = this.executor.schedule(this, initialDelay.toMillis(), TimeUnit.MILLISECONDS);
83+
this.currentFuture = this.executor.schedule(this, initialDelay.toNanos(), TimeUnit.NANOSECONDS);
8484
return this;
8585
}
8686
}
@@ -158,8 +158,8 @@ public int compareTo(Delayed other) {
158158
if (this == other) {
159159
return 0;
160160
}
161-
long diff = getDelay(TimeUnit.MILLISECONDS) - other.getDelay(TimeUnit.MILLISECONDS);
162-
return (diff == 0 ? 0 : ((diff < 0)? -1 : 1));
161+
long diff = getDelay(TimeUnit.NANOSECONDS) - other.getDelay(TimeUnit.NANOSECONDS);
162+
return (diff == 0 ? 0 : (diff < 0 ? -1 : 1));
163163
}
164164

165165
}

spring-context/src/main/java/org/springframework/scheduling/concurrent/ThreadPoolTaskScheduler.java

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -382,7 +382,7 @@ public ScheduledFuture<?> schedule(Runnable task, Instant startTime) {
382382
ScheduledExecutorService executor = getScheduledExecutor();
383383
Duration initialDelay = Duration.between(this.clock.instant(), startTime);
384384
try {
385-
return executor.schedule(errorHandlingTask(task, false), initialDelay.toMillis(), TimeUnit.MILLISECONDS);
385+
return executor.schedule(errorHandlingTask(task, false), initialDelay.toNanos(), TimeUnit.NANOSECONDS);
386386
}
387387
catch (RejectedExecutionException ex) {
388388
throw new TaskRejectedException("Executor [" + executor + "] did not accept task: " + task, ex);
@@ -394,7 +394,7 @@ public ScheduledFuture<?> scheduleAtFixedRate(Runnable task, Instant startTime,
394394
ScheduledExecutorService executor = getScheduledExecutor();
395395
Duration initialDelay = Duration.between(this.clock.instant(), startTime);
396396
try {
397-
return executor.scheduleAtFixedRate(errorHandlingTask(task, true), initialDelay.toMillis(), period.toMillis(), TimeUnit.MILLISECONDS);
397+
return executor.scheduleAtFixedRate(errorHandlingTask(task, true), initialDelay.toNanos(), period.toNanos(), TimeUnit.NANOSECONDS);
398398
}
399399
catch (RejectedExecutionException ex) {
400400
throw new TaskRejectedException("Executor [" + executor + "] did not accept task: " + task, ex);
@@ -405,7 +405,7 @@ public ScheduledFuture<?> scheduleAtFixedRate(Runnable task, Instant startTime,
405405
public ScheduledFuture<?> scheduleAtFixedRate(Runnable task, Duration period) {
406406
ScheduledExecutorService executor = getScheduledExecutor();
407407
try {
408-
return executor.scheduleAtFixedRate(errorHandlingTask(task, true), 0, period.toMillis(), TimeUnit.MILLISECONDS);
408+
return executor.scheduleAtFixedRate(errorHandlingTask(task, true), 0, period.toNanos(), TimeUnit.NANOSECONDS);
409409
}
410410
catch (RejectedExecutionException ex) {
411411
throw new TaskRejectedException("Executor [" + executor + "] did not accept task: " + task, ex);
@@ -417,7 +417,7 @@ public ScheduledFuture<?> scheduleWithFixedDelay(Runnable task, Instant startTim
417417
ScheduledExecutorService executor = getScheduledExecutor();
418418
Duration initialDelay = Duration.between(this.clock.instant(), startTime);
419419
try {
420-
return executor.scheduleWithFixedDelay(errorHandlingTask(task, true), initialDelay.toMillis(), delay.toMillis(), TimeUnit.MILLISECONDS);
420+
return executor.scheduleWithFixedDelay(errorHandlingTask(task, true), initialDelay.toNanos(), delay.toNanos(), TimeUnit.NANOSECONDS);
421421
}
422422
catch (RejectedExecutionException ex) {
423423
throw new TaskRejectedException("Executor [" + executor + "] did not accept task: " + task, ex);
@@ -428,7 +428,7 @@ public ScheduledFuture<?> scheduleWithFixedDelay(Runnable task, Instant startTim
428428
public ScheduledFuture<?> scheduleWithFixedDelay(Runnable task, Duration delay) {
429429
ScheduledExecutorService executor = getScheduledExecutor();
430430
try {
431-
return executor.scheduleWithFixedDelay(errorHandlingTask(task, true), 0, delay.toMillis(), TimeUnit.MILLISECONDS);
431+
return executor.scheduleWithFixedDelay(errorHandlingTask(task, true), 0, delay.toNanos(), TimeUnit.NANOSECONDS);
432432
}
433433
catch (RejectedExecutionException ex) {
434434
throw new TaskRejectedException("Executor [" + executor + "] did not accept task: " + task, ex);

spring-context/src/main/java/org/springframework/scheduling/config/ScheduledTaskRegistrar.java

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -485,7 +485,7 @@ public ScheduledTask scheduleFixedRateTask(FixedRateTask task) {
485485
}
486486
if (this.taskScheduler != null) {
487487
Duration initialDelay = task.getInitialDelayDuration();
488-
if (initialDelay.toMillis() > 0) {
488+
if (initialDelay.toNanos() > 0) {
489489
Instant startTime = this.taskScheduler.getClock().instant().plus(initialDelay);
490490
scheduledTask.future =
491491
this.taskScheduler.scheduleAtFixedRate(task.getRunnable(), startTime, task.getIntervalDuration());

spring-context/src/main/java/org/springframework/scheduling/support/CronExpression.java

Lines changed: 10 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -57,18 +57,12 @@ public final class CronExpression {
5757
private final String expression;
5858

5959

60-
private CronExpression(
61-
CronField seconds,
62-
CronField minutes,
63-
CronField hours,
64-
CronField daysOfMonth,
65-
CronField months,
66-
CronField daysOfWeek,
67-
String expression) {
60+
private CronExpression(CronField seconds, CronField minutes, CronField hours,
61+
CronField daysOfMonth, CronField months, CronField daysOfWeek, String expression) {
6862

69-
// reverse order, to make big changes first
70-
// to make sure we end up at 0 nanos, we add an extra field
71-
this.fields = new CronField[]{daysOfWeek, months, daysOfMonth, hours, minutes, seconds, CronField.zeroNanos()};
63+
// Reverse order, to make big changes first.
64+
// To make sure we end up at 0 nanos, we add an extra field.
65+
this.fields = new CronField[] {daysOfWeek, months, daysOfMonth, hours, minutes, seconds, CronField.zeroNanos()};
7266
this.expression = expression;
7367
}
7468

@@ -268,26 +262,18 @@ private <T extends Temporal & Comparable<? super T>> T nextOrSameInternal(T temp
268262

269263

270264
@Override
271-
public int hashCode() {
272-
return Arrays.hashCode(this.fields);
265+
public boolean equals(@Nullable Object other) {
266+
return (this == other || (other instanceof CronExpression that &&
267+
Arrays.equals(this.fields, that.fields)));
273268
}
274269

275270
@Override
276-
public boolean equals(@Nullable Object o) {
277-
if (this == o) {
278-
return true;
279-
}
280-
if (o instanceof CronExpression other) {
281-
return Arrays.equals(this.fields, other.fields);
282-
}
283-
else {
284-
return false;
285-
}
271+
public int hashCode() {
272+
return Arrays.hashCode(this.fields);
286273
}
287274

288275
/**
289276
* Return the expression string used to create this {@code CronExpression}.
290-
* @return the expression string
291277
*/
292278
@Override
293279
public String toString() {

spring-context/src/main/java/org/springframework/scheduling/support/CronTrigger.java

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -89,7 +89,7 @@ public String getExpression() {
8989
/**
9090
* Determine the next execution time according to the given trigger context.
9191
* <p>Next execution times are calculated based on the
92-
* {@linkplain TriggerContext#lastCompletionTime completion time} of the
92+
* {@linkplain TriggerContext#lastCompletion completion time} of the
9393
* previous execution; therefore, overlapping executions won't occur.
9494
*/
9595
@Override
@@ -114,8 +114,9 @@ public Instant nextExecution(TriggerContext triggerContext) {
114114

115115

116116
@Override
117-
public boolean equals(@Nullable Object obj) {
118-
return (this == obj || (obj instanceof CronTrigger that && this.expression.equals(that.expression)));
117+
public boolean equals(@Nullable Object other) {
118+
return (this == other || (other instanceof CronTrigger that &&
119+
this.expression.equals(that.expression)));
119120
}
120121

121122
@Override

0 commit comments

Comments
 (0)