Affects: All versions, tested with 4.3.10 and verified to still be a problem in tip of master ec97089
It seems that Spring task scheduler implementation still uses old java.util.Date for some of its timings (esp. tasks delayed by time) and it is not possible to easily inject a different clock source, such as java.time.Clock bean instance or via SchedulerConfigurer.
Such a feature would be very valuable for unit testing time-dependent functionality, which now requires use of PowerMock or JMockit to directly patch System.currentTimeMillis, Clock.systemUTC or both.
Comment From: jhoeller
So you'd like to specify a java.time.Clock
bean through SchedulingConfigurer
, expecting it to be picked up in the runtime scheduling setup wherever we're currently calling System.currentTimeMillis()
for scheduling decisions? That seems feasible, and aligned with where Bean Validation 2.0 went with its ValidatorFactory.getClockProvider()
method.
I'm not sure we need a system-wide Clock
bean at the core context level since we're only really using timestamps for logging and monitoring elsewhere, not for time-based decisions, so I suppose we would still prefer the real clock for those purposes even in a test setup.
Comment From: jhoeller
A related recent request for a configurable clock bean to be provided for test setups: #24884 - we'll try to revisit the topic for 5.3.
Comment From: jhoeller
If you were specifying a custom Clock
bean for scheduling purposes, what exactly would you expect it to be used for? All time taking, including monitoring and metrics such as actual execution time, or just time-based decisions such as trigger calculations? Would you try to used a fixed clock (always returning the same timestamp) or rather some kind of custom mutable clock?
Comment From: jhoeller
I've added Clock
exposure to TaskScheduler
and TriggerContext
, consistently used within our implementation instead of System.currentTimeMillis()
and new Date()
calls now, configurable via setClock
calls on ThreadPoolTaskScheduler
and ConcurrentTaskScheduler
.
Comment From: weiwang107
Is this really working? In my setup (spring-boot 2.7.5), I tried this to fixed the clock. However, the scheduled method that annotated with @Scheduled is still trigger periodically.
The following is what I tried to configure the scheduler
@Configuration
@EnableScheduling
public class SchedulerConfig implements SchedulingConfigurer {
@Override
public void configureTasks(ScheduledTaskRegistrar taskRegistrar) {
taskRegistrar.setScheduler(taskScheduler());
taskRegistrar.setTaskScheduler(taskScheduler());
}
TaskScheduler taskScheduler(){
ThreadPoolTaskScheduler threadPoolTaskScheduler = new ThreadPoolTaskScheduler();
threadPoolTaskScheduler.setPoolSize(5);
threadPoolTaskScheduler.setClock(Clock.fixed(Instant.now(), ZoneId.systemDefault()));
threadPoolTaskScheduler.setThreadNamePrefix("SimpleAsyncTaskExecutor-");
threadPoolTaskScheduler.initialize();
return threadPoolTaskScheduler;
}
}
Comment From: bclozel
Unless I'm mistaken this is working as designed. The clock is used for calculating the next triggers. A fixed clock is not supposed to "stop time and prevent all executions".
Comment From: fisco-unimatic
@weiwang107 Like you, I was hoping that setting the clock would allow me to control when the scheduled method would trigger. However, from debugging the code, it seems that the clock only determines the first execution time (see PeriodicTrigger#nextExecutionTime
).
After that first run, it will just wait for the annotated delay, without looking at the clock again.
To decide whether the next execution time has been reached yet, AbstractQueuedSynchronizer.ConditionObject#awaitNanos
doesn't look at the supplied clock - it uses System#nanoTime
.