Skip to content

Commit

Permalink
Use nanosecond precision for scheduling (aligned with calculations)
Browse files Browse the repository at this point in the history
Closes gh-30666
  • Loading branch information
jhoeller committed Jun 14, 2023
1 parent a73ad52 commit 3415b04
Show file tree
Hide file tree
Showing 7 changed files with 33 additions and 43 deletions.
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
/*
* Copyright 2002-2022 the original author or authors.
* Copyright 2002-2023 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
Expand Down Expand Up @@ -27,6 +27,7 @@
* of a given task.
*
* @author Juergen Hoeller
* @author Arjen Poutsma
* @since 3.0
*/
public interface TriggerContext {
Expand Down Expand Up @@ -78,6 +79,7 @@ default Date lastActualExecutionTime() {
/**
* Return the last <i>actual</i> execution time of the task,
* or {@code null} if not scheduled before.
* @since 6.0
*/
@Nullable
Instant lastActualExecution();
Expand All @@ -98,6 +100,7 @@ default Date lastCompletionTime() {
/**
* Return the last completion time of the task,
* or {@code null} if not scheduled before.
* @since 6.0
*/
@Nullable
Instant lastCompletion();
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -211,7 +211,7 @@ public ScheduledFuture<?> schedule(Runnable task, Trigger trigger) {
public ScheduledFuture<?> schedule(Runnable task, Instant startTime) {
Duration initialDelay = Duration.between(this.clock.instant(), startTime);
try {
return this.scheduledExecutor.schedule(decorateTask(task, false), initialDelay.toMillis(), TimeUnit.MILLISECONDS);
return this.scheduledExecutor.schedule(decorateTask(task, false), initialDelay.toNanos(), TimeUnit.NANOSECONDS);
}
catch (RejectedExecutionException ex) {
throw new TaskRejectedException("Executor [" + this.scheduledExecutor + "] did not accept task: " + task, ex);
Expand All @@ -222,7 +222,7 @@ public ScheduledFuture<?> schedule(Runnable task, Instant startTime) {
public ScheduledFuture<?> scheduleAtFixedRate(Runnable task, Instant startTime, Duration period) {
Duration initialDelay = Duration.between(this.clock.instant(), startTime);
try {
return this.scheduledExecutor.scheduleAtFixedRate(decorateTask(task, true), initialDelay.toMillis(), period.toMillis(), TimeUnit.MILLISECONDS);
return this.scheduledExecutor.scheduleAtFixedRate(decorateTask(task, true), initialDelay.toNanos(), period.toNanos(), TimeUnit.NANOSECONDS);
}
catch (RejectedExecutionException ex) {
throw new TaskRejectedException("Executor [" + this.scheduledExecutor + "] did not accept task: " + task, ex);
Expand All @@ -232,7 +232,7 @@ public ScheduledFuture<?> scheduleAtFixedRate(Runnable task, Instant startTime,
@Override
public ScheduledFuture<?> scheduleAtFixedRate(Runnable task, Duration period) {
try {
return this.scheduledExecutor.scheduleAtFixedRate(decorateTask(task, true), 0, period.toMillis(), TimeUnit.MILLISECONDS);
return this.scheduledExecutor.scheduleAtFixedRate(decorateTask(task, true), 0, period.toNanos(), TimeUnit.NANOSECONDS);
}
catch (RejectedExecutionException ex) {
throw new TaskRejectedException("Executor [" + this.scheduledExecutor + "] did not accept task: " + task, ex);
Expand All @@ -243,7 +243,7 @@ public ScheduledFuture<?> scheduleAtFixedRate(Runnable task, Duration period) {
public ScheduledFuture<?> scheduleWithFixedDelay(Runnable task, Instant startTime, Duration delay) {
Duration initialDelay = Duration.between(this.clock.instant(), startTime);
try {
return this.scheduledExecutor.scheduleWithFixedDelay(decorateTask(task, true), initialDelay.toMillis(), delay.toMillis(), TimeUnit.MILLISECONDS);
return this.scheduledExecutor.scheduleWithFixedDelay(decorateTask(task, true), initialDelay.toNanos(), delay.toNanos(), TimeUnit.NANOSECONDS);
}
catch (RejectedExecutionException ex) {
throw new TaskRejectedException("Executor [" + this.scheduledExecutor + "] did not accept task: " + task, ex);
Expand All @@ -253,7 +253,7 @@ public ScheduledFuture<?> scheduleWithFixedDelay(Runnable task, Instant startTim
@Override
public ScheduledFuture<?> scheduleWithFixedDelay(Runnable task, Duration delay) {
try {
return this.scheduledExecutor.scheduleWithFixedDelay(decorateTask(task, true), 0, delay.toMillis(), TimeUnit.MILLISECONDS);
return this.scheduledExecutor.scheduleWithFixedDelay(decorateTask(task, true), 0, delay.toNanos(), TimeUnit.NANOSECONDS);
}
catch (RejectedExecutionException ex) {
throw new TaskRejectedException("Executor [" + this.scheduledExecutor + "] did not accept task: " + task, ex);
Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
/*
* Copyright 2002-2022 the original author or authors.
* Copyright 2002-2023 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
Expand Down Expand Up @@ -80,7 +80,7 @@ public ScheduledFuture<?> schedule() {
return null;
}
Duration initialDelay = Duration.between(this.triggerContext.getClock().instant(), this.scheduledExecutionTime);
this.currentFuture = this.executor.schedule(this, initialDelay.toMillis(), TimeUnit.MILLISECONDS);
this.currentFuture = this.executor.schedule(this, initialDelay.toNanos(), TimeUnit.NANOSECONDS);
return this;
}
}
Expand Down Expand Up @@ -158,8 +158,8 @@ public int compareTo(Delayed other) {
if (this == other) {
return 0;
}
long diff = getDelay(TimeUnit.MILLISECONDS) - other.getDelay(TimeUnit.MILLISECONDS);
return (diff == 0 ? 0 : ((diff < 0)? -1 : 1));
long diff = getDelay(TimeUnit.NANOSECONDS) - other.getDelay(TimeUnit.NANOSECONDS);
return (diff == 0 ? 0 : (diff < 0 ? -1 : 1));
}

}
Original file line number Diff line number Diff line change
Expand Up @@ -382,7 +382,7 @@ public ScheduledFuture<?> schedule(Runnable task, Instant startTime) {
ScheduledExecutorService executor = getScheduledExecutor();
Duration initialDelay = Duration.between(this.clock.instant(), startTime);
try {
return executor.schedule(errorHandlingTask(task, false), initialDelay.toMillis(), TimeUnit.MILLISECONDS);
return executor.schedule(errorHandlingTask(task, false), initialDelay.toNanos(), TimeUnit.NANOSECONDS);
}
catch (RejectedExecutionException ex) {
throw new TaskRejectedException("Executor [" + executor + "] did not accept task: " + task, ex);
Expand All @@ -394,7 +394,7 @@ public ScheduledFuture<?> scheduleAtFixedRate(Runnable task, Instant startTime,
ScheduledExecutorService executor = getScheduledExecutor();
Duration initialDelay = Duration.between(this.clock.instant(), startTime);
try {
return executor.scheduleAtFixedRate(errorHandlingTask(task, true), initialDelay.toMillis(), period.toMillis(), TimeUnit.MILLISECONDS);
return executor.scheduleAtFixedRate(errorHandlingTask(task, true), initialDelay.toNanos(), period.toNanos(), TimeUnit.NANOSECONDS);
}
catch (RejectedExecutionException ex) {
throw new TaskRejectedException("Executor [" + executor + "] did not accept task: " + task, ex);
Expand All @@ -405,7 +405,7 @@ public ScheduledFuture<?> scheduleAtFixedRate(Runnable task, Instant startTime,
public ScheduledFuture<?> scheduleAtFixedRate(Runnable task, Duration period) {
ScheduledExecutorService executor = getScheduledExecutor();
try {
return executor.scheduleAtFixedRate(errorHandlingTask(task, true), 0, period.toMillis(), TimeUnit.MILLISECONDS);
return executor.scheduleAtFixedRate(errorHandlingTask(task, true), 0, period.toNanos(), TimeUnit.NANOSECONDS);
}
catch (RejectedExecutionException ex) {
throw new TaskRejectedException("Executor [" + executor + "] did not accept task: " + task, ex);
Expand All @@ -417,7 +417,7 @@ public ScheduledFuture<?> scheduleWithFixedDelay(Runnable task, Instant startTim
ScheduledExecutorService executor = getScheduledExecutor();
Duration initialDelay = Duration.between(this.clock.instant(), startTime);
try {
return executor.scheduleWithFixedDelay(errorHandlingTask(task, true), initialDelay.toMillis(), delay.toMillis(), TimeUnit.MILLISECONDS);
return executor.scheduleWithFixedDelay(errorHandlingTask(task, true), initialDelay.toNanos(), delay.toNanos(), TimeUnit.NANOSECONDS);
}
catch (RejectedExecutionException ex) {
throw new TaskRejectedException("Executor [" + executor + "] did not accept task: " + task, ex);
Expand All @@ -428,7 +428,7 @@ public ScheduledFuture<?> scheduleWithFixedDelay(Runnable task, Instant startTim
public ScheduledFuture<?> scheduleWithFixedDelay(Runnable task, Duration delay) {
ScheduledExecutorService executor = getScheduledExecutor();
try {
return executor.scheduleWithFixedDelay(errorHandlingTask(task, true), 0, delay.toMillis(), TimeUnit.MILLISECONDS);
return executor.scheduleWithFixedDelay(errorHandlingTask(task, true), 0, delay.toNanos(), TimeUnit.NANOSECONDS);
}
catch (RejectedExecutionException ex) {
throw new TaskRejectedException("Executor [" + executor + "] did not accept task: " + task, ex);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -485,7 +485,7 @@ public ScheduledTask scheduleFixedRateTask(FixedRateTask task) {
}
if (this.taskScheduler != null) {
Duration initialDelay = task.getInitialDelayDuration();
if (initialDelay.toMillis() > 0) {
if (initialDelay.toNanos() > 0) {
Instant startTime = this.taskScheduler.getClock().instant().plus(initialDelay);
scheduledTask.future =
this.taskScheduler.scheduleAtFixedRate(task.getRunnable(), startTime, task.getIntervalDuration());
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -57,18 +57,12 @@ public final class CronExpression {
private final String expression;


private CronExpression(
CronField seconds,
CronField minutes,
CronField hours,
CronField daysOfMonth,
CronField months,
CronField daysOfWeek,
String expression) {
private CronExpression(CronField seconds, CronField minutes, CronField hours,
CronField daysOfMonth, CronField months, CronField daysOfWeek, String expression) {

// reverse order, to make big changes first
// to make sure we end up at 0 nanos, we add an extra field
this.fields = new CronField[]{daysOfWeek, months, daysOfMonth, hours, minutes, seconds, CronField.zeroNanos()};
// Reverse order, to make big changes first.
// To make sure we end up at 0 nanos, we add an extra field.
this.fields = new CronField[] {daysOfWeek, months, daysOfMonth, hours, minutes, seconds, CronField.zeroNanos()};
this.expression = expression;
}

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


@Override
public int hashCode() {
return Arrays.hashCode(this.fields);
public boolean equals(@Nullable Object other) {
return (this == other || (other instanceof CronExpression that &&
Arrays.equals(this.fields, that.fields)));
}

@Override
public boolean equals(@Nullable Object o) {
if (this == o) {
return true;
}
if (o instanceof CronExpression other) {
return Arrays.equals(this.fields, other.fields);
}
else {
return false;
}
public int hashCode() {
return Arrays.hashCode(this.fields);
}

/**
* Return the expression string used to create this {@code CronExpression}.
* @return the expression string
*/
@Override
public String toString() {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -89,7 +89,7 @@ public String getExpression() {
/**
* Determine the next execution time according to the given trigger context.
* <p>Next execution times are calculated based on the
* {@linkplain TriggerContext#lastCompletionTime completion time} of the
* {@linkplain TriggerContext#lastCompletion completion time} of the
* previous execution; therefore, overlapping executions won't occur.
*/
@Override
Expand All @@ -114,8 +114,9 @@ public Instant nextExecution(TriggerContext triggerContext) {


@Override
public boolean equals(@Nullable Object obj) {
return (this == obj || (obj instanceof CronTrigger that && this.expression.equals(that.expression)));
public boolean equals(@Nullable Object other) {
return (this == other || (other instanceof CronTrigger that &&
this.expression.equals(that.expression)));
}

@Override
Expand Down

0 comments on commit 3415b04

Please sign in to comment.