Configurable java.time.Clock on TaskScheduler implementations

Closes gh-25782
This commit is contained in:
Juergen Hoeller 2020-09-18 10:21:48 +02:00
parent 5f587faffa
commit 051de3f179
8 changed files with 107 additions and 25 deletions

View File

@ -1,5 +1,5 @@
/*
* Copyright 2002-2016 the original author or authors.
* Copyright 2002-2020 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.
@ -16,6 +16,7 @@
package org.springframework.scheduling;
import java.time.Clock;
import java.time.Duration;
import java.time.Instant;
import java.util.Date;
@ -49,6 +50,15 @@ import org.springframework.lang.Nullable;
*/
public interface TaskScheduler {
/**
* Return the clock to use for scheduling purposes.
* @since 5.3
* @see Clock#systemDefaultZone()
*/
default Clock getClock() {
return Clock.systemDefaultZone();
}
/**
* Schedule the given {@link Runnable}, invoking it whenever the trigger
* indicates a next execution time.

View File

@ -1,5 +1,5 @@
/*
* Copyright 2002-2012 the original author or authors.
* Copyright 2002-2020 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.
@ -16,6 +16,7 @@
package org.springframework.scheduling;
import java.time.Clock;
import java.util.Date;
import org.springframework.lang.Nullable;
@ -29,6 +30,16 @@ import org.springframework.lang.Nullable;
*/
public interface TriggerContext {
/**
* Return the clock to use for trigger calculation.
* @since 5.3
* @see TaskScheduler#getClock()
* @see Clock#systemDefaultZone()
*/
default Clock getClock() {
return Clock.systemDefaultZone();
}
/**
* Return the last <i>scheduled</i> execution time of the task,
* or {@code null} if not scheduled before.

View File

@ -1,5 +1,5 @@
/*
* Copyright 2002-2017 the original author or authors.
* Copyright 2002-2020 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.
@ -16,6 +16,7 @@
package org.springframework.scheduling.concurrent;
import java.time.Clock;
import java.util.Date;
import java.util.concurrent.Executor;
import java.util.concurrent.Executors;
@ -89,6 +90,8 @@ public class ConcurrentTaskScheduler extends ConcurrentTaskExecutor implements T
@Nullable
private ErrorHandler errorHandler;
private Clock clock = Clock.systemDefaultZone();
/**
* Create a new ConcurrentTaskScheduler,
@ -168,6 +171,21 @@ public class ConcurrentTaskScheduler extends ConcurrentTaskExecutor implements T
this.errorHandler = errorHandler;
}
/**
* Set the clock to use for scheduling purposes.
* <p>The default clock is the system clock for the default time zone.
* @since 5.3
* @see Clock#systemDefaultZone()
*/
public void setClock(Clock clock) {
this.clock = clock;
}
@Override
public Clock getClock() {
return this.clock;
}
@Override
@Nullable
@ -179,7 +197,7 @@ public class ConcurrentTaskScheduler extends ConcurrentTaskExecutor implements T
else {
ErrorHandler errorHandler =
(this.errorHandler != null ? this.errorHandler : TaskUtils.getDefaultErrorHandler(true));
return new ReschedulingRunnable(task, trigger, this.scheduledExecutor, errorHandler).schedule();
return new ReschedulingRunnable(task, trigger, this.clock, this.scheduledExecutor, errorHandler).schedule();
}
}
catch (RejectedExecutionException ex) {
@ -189,7 +207,7 @@ public class ConcurrentTaskScheduler extends ConcurrentTaskExecutor implements T
@Override
public ScheduledFuture<?> schedule(Runnable task, Date startTime) {
long initialDelay = startTime.getTime() - System.currentTimeMillis();
long initialDelay = startTime.getTime() - this.clock.millis();
try {
return this.scheduledExecutor.schedule(decorateTask(task, false), initialDelay, TimeUnit.MILLISECONDS);
}
@ -200,7 +218,7 @@ public class ConcurrentTaskScheduler extends ConcurrentTaskExecutor implements T
@Override
public ScheduledFuture<?> scheduleAtFixedRate(Runnable task, Date startTime, long period) {
long initialDelay = startTime.getTime() - System.currentTimeMillis();
long initialDelay = startTime.getTime() - this.clock.millis();
try {
return this.scheduledExecutor.scheduleAtFixedRate(decorateTask(task, true), initialDelay, period, TimeUnit.MILLISECONDS);
}
@ -221,7 +239,7 @@ public class ConcurrentTaskScheduler extends ConcurrentTaskExecutor implements T
@Override
public ScheduledFuture<?> scheduleWithFixedDelay(Runnable task, Date startTime, long delay) {
long initialDelay = startTime.getTime() - System.currentTimeMillis();
long initialDelay = startTime.getTime() - this.clock.millis();
try {
return this.scheduledExecutor.scheduleWithFixedDelay(decorateTask(task, true), initialDelay, delay, TimeUnit.MILLISECONDS);
}

View File

@ -1,5 +1,5 @@
/*
* Copyright 2002-2017 the original author or authors.
* Copyright 2002-2020 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.
@ -16,6 +16,7 @@
package org.springframework.scheduling.concurrent;
import java.time.Clock;
import java.util.Date;
import java.util.concurrent.Delayed;
import java.util.concurrent.ExecutionException;
@ -47,7 +48,7 @@ class ReschedulingRunnable extends DelegatingErrorHandlingRunnable implements Sc
private final Trigger trigger;
private final SimpleTriggerContext triggerContext = new SimpleTriggerContext();
private final SimpleTriggerContext triggerContext;
private final ScheduledExecutorService executor;
@ -60,11 +61,12 @@ class ReschedulingRunnable extends DelegatingErrorHandlingRunnable implements Sc
private final Object triggerContextMonitor = new Object();
public ReschedulingRunnable(
Runnable delegate, Trigger trigger, ScheduledExecutorService executor, ErrorHandler errorHandler) {
public ReschedulingRunnable(Runnable delegate, Trigger trigger, Clock clock,
ScheduledExecutorService executor, ErrorHandler errorHandler) {
super(delegate, errorHandler);
this.trigger = trigger;
this.triggerContext = new SimpleTriggerContext(clock);
this.executor = executor;
}
@ -76,7 +78,7 @@ class ReschedulingRunnable extends DelegatingErrorHandlingRunnable implements Sc
if (this.scheduledExecutionTime == null) {
return null;
}
long initialDelay = this.scheduledExecutionTime.getTime() - System.currentTimeMillis();
long initialDelay = this.scheduledExecutionTime.getTime() - this.triggerContext.getClock().millis();
this.currentFuture = this.executor.schedule(this, initialDelay, TimeUnit.MILLISECONDS);
return this;
}
@ -89,9 +91,9 @@ class ReschedulingRunnable extends DelegatingErrorHandlingRunnable implements Sc
@Override
public void run() {
Date actualExecutionTime = new Date();
Date actualExecutionTime = new Date(this.triggerContext.getClock().millis());
super.run();
Date completionTime = new Date();
Date completionTime = new Date(this.triggerContext.getClock().millis());
synchronized (this.triggerContextMonitor) {
Assert.state(this.scheduledExecutionTime != null, "No scheduled execution");
this.triggerContext.update(this.scheduledExecutionTime, actualExecutionTime, completionTime);

View File

@ -16,6 +16,7 @@
package org.springframework.scheduling.concurrent;
import java.time.Clock;
import java.util.Date;
import java.util.Map;
import java.util.concurrent.Callable;
@ -66,6 +67,8 @@ public class ThreadPoolTaskScheduler extends ExecutorConfigurationSupport
@Nullable
private volatile ErrorHandler errorHandler;
private Clock clock = Clock.systemDefaultZone();
@Nullable
private ScheduledExecutorService scheduledExecutor;
@ -110,6 +113,21 @@ public class ThreadPoolTaskScheduler extends ExecutorConfigurationSupport
this.errorHandler = errorHandler;
}
/**
* Set the clock to use for scheduling purposes.
* <p>The default clock is the system clock for the default time zone.
* @since 5.3
* @see Clock#systemDefaultZone()
*/
public void setClock(Clock clock) {
this.clock = clock;
}
@Override
public Clock getClock() {
return this.clock;
}
@Override
protected ExecutorService initializeExecutor(
@ -310,7 +328,7 @@ public class ThreadPoolTaskScheduler extends ExecutorConfigurationSupport
if (errorHandler == null) {
errorHandler = TaskUtils.getDefaultErrorHandler(true);
}
return new ReschedulingRunnable(task, trigger, executor, errorHandler).schedule();
return new ReschedulingRunnable(task, trigger, this.clock, executor, errorHandler).schedule();
}
catch (RejectedExecutionException ex) {
throw new TaskRejectedException("Executor [" + executor + "] did not accept task: " + task, ex);
@ -320,7 +338,7 @@ public class ThreadPoolTaskScheduler extends ExecutorConfigurationSupport
@Override
public ScheduledFuture<?> schedule(Runnable task, Date startTime) {
ScheduledExecutorService executor = getScheduledExecutor();
long initialDelay = startTime.getTime() - System.currentTimeMillis();
long initialDelay = startTime.getTime() - this.clock.millis();
try {
return executor.schedule(errorHandlingTask(task, false), initialDelay, TimeUnit.MILLISECONDS);
}
@ -332,7 +350,7 @@ public class ThreadPoolTaskScheduler extends ExecutorConfigurationSupport
@Override
public ScheduledFuture<?> scheduleAtFixedRate(Runnable task, Date startTime, long period) {
ScheduledExecutorService executor = getScheduledExecutor();
long initialDelay = startTime.getTime() - System.currentTimeMillis();
long initialDelay = startTime.getTime() - this.clock.millis();
try {
return executor.scheduleAtFixedRate(errorHandlingTask(task, true), initialDelay, period, TimeUnit.MILLISECONDS);
}
@ -355,7 +373,7 @@ public class ThreadPoolTaskScheduler extends ExecutorConfigurationSupport
@Override
public ScheduledFuture<?> scheduleWithFixedDelay(Runnable task, Date startTime, long delay) {
ScheduledExecutorService executor = getScheduledExecutor();
long initialDelay = startTime.getTime() - System.currentTimeMillis();
long initialDelay = startTime.getTime() - this.clock.millis();
try {
return executor.scheduleWithFixedDelay(errorHandlingTask(task, true), initialDelay, delay, TimeUnit.MILLISECONDS);
}

View File

@ -1,5 +1,5 @@
/*
* Copyright 2002-2019 the original author or authors.
* Copyright 2002-2020 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.
@ -470,7 +470,7 @@ public class ScheduledTaskRegistrar implements ScheduledTaskHolder, Initializing
}
if (this.taskScheduler != null) {
if (task.getInitialDelay() > 0) {
Date startTime = new Date(System.currentTimeMillis() + task.getInitialDelay());
Date startTime = new Date(this.taskScheduler.getClock().millis() + task.getInitialDelay());
scheduledTask.future =
this.taskScheduler.scheduleAtFixedRate(task.getRunnable(), startTime, task.getInterval());
}
@ -519,7 +519,7 @@ public class ScheduledTaskRegistrar implements ScheduledTaskHolder, Initializing
}
if (this.taskScheduler != null) {
if (task.getInitialDelay() > 0) {
Date startTime = new Date(System.currentTimeMillis() + task.getInitialDelay());
Date startTime = new Date(this.taskScheduler.getClock().millis() + task.getInitialDelay());
scheduledTask.future =
this.taskScheduler.scheduleWithFixedDelay(task.getRunnable(), startTime, task.getInterval());
}

View File

@ -134,7 +134,7 @@ public class PeriodicTrigger implements Trigger {
Date lastExecution = triggerContext.lastScheduledExecutionTime();
Date lastCompletion = triggerContext.lastCompletionTime();
if (lastExecution == null || lastCompletion == null) {
return new Date(System.currentTimeMillis() + this.initialDelay);
return new Date(triggerContext.getClock().millis() + this.initialDelay);
}
if (this.fixedRate) {
return new Date(lastExecution.getTime() + this.period);

View File

@ -1,5 +1,5 @@
/*
* Copyright 2002-2018 the original author or authors.
* Copyright 2002-2020 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.
@ -16,6 +16,7 @@
package org.springframework.scheduling.support;
import java.time.Clock;
import java.util.Date;
import org.springframework.lang.Nullable;
@ -29,6 +30,8 @@ import org.springframework.scheduling.TriggerContext;
*/
public class SimpleTriggerContext implements TriggerContext {
private final Clock clock;
@Nullable
private volatile Date lastScheduledExecutionTime;
@ -40,23 +43,38 @@ public class SimpleTriggerContext implements TriggerContext {
/**
* Create a SimpleTriggerContext with all time values set to {@code null}.
* Create a SimpleTriggerContext with all time values set to {@code null},
* exposing the system clock for the default time zone.
*/
public SimpleTriggerContext() {
this.clock = Clock.systemDefaultZone();
}
/**
* Create a SimpleTriggerContext with the given time values.
* Create a SimpleTriggerContext with the given time values,
* exposing the system clock for the default time zone.
* @param lastScheduledExecutionTime last <i>scheduled</i> execution time
* @param lastActualExecutionTime last <i>actual</i> execution time
* @param lastCompletionTime last completion time
*/
public SimpleTriggerContext(Date lastScheduledExecutionTime, Date lastActualExecutionTime, Date lastCompletionTime) {
this();
this.lastScheduledExecutionTime = lastScheduledExecutionTime;
this.lastActualExecutionTime = lastActualExecutionTime;
this.lastCompletionTime = lastCompletionTime;
}
/**
* Create a SimpleTriggerContext with all time values set to {@code null},
* exposing the given clock.
* @param clock the clock to use for trigger calculation
* @since 5.3
* @see #update(Date, Date, Date)
*/
public SimpleTriggerContext(Clock clock) {
this.clock = clock;
}
/**
* Update this holder's state with the latest time values.
@ -71,6 +89,11 @@ public class SimpleTriggerContext implements TriggerContext {
}
@Override
public Clock getClock() {
return this.clock;
}
@Override
@Nullable
public Date lastScheduledExecutionTime() {