As mentioned in SPR-10556, overlapping executions won't occur.
But in my project, I have a scheduled task which has to run every hour (with cron expression 0 20 * * * ?) .In most case, it took 20 to 30 minutes. But sometimes, it took 2 to 3 hours due to force majeure. Though there is a way to solve it by submitting the task to a separate thread pool, it would be better if spring-context support overlapping executions.
For now, I solve it by the following codes:
- BaseOnLSETCronTrigger.java which copy from org.springframework.scheduling.support.CronTrigger but changed in
/**
* Determine the next execution time according to the given trigger context.
* <p>Next execution times are calculated based on the
* {@linkplain TriggerContext#lastScheduledExecutionTime completion time} of the
* previous execution; therefore, overlapping executions will occur on purpose.
*/
@Override
public Date nextExecutionTime(TriggerContext triggerContext) {
Date date = triggerContext.lastScheduledExecutionTime();
if (date == null) {
date = new Date();
}
return this.sequenceGenerator.next(date);
}
- ReschedulingImmediatelyRunnable.java copy from org.springframework.scheduling.concurrent.ReschedulingRunnable and changed in
/**
* we firstly call schedule() and then call super.run() to keep
* tasks which take lots of time from delaying the schedule
*/
@Override
public void run() {
Date actualExecutionTime = new Date();
this.triggerContext.update(this.scheduledExecutionTime, actualExecutionTime, null);
if (!obtainCurrentFuture().isCancelled()) {
schedule();//schedule first
}
super.run();//then run
Date completionTime = new Date();
synchronized (this.triggerContextMonitor) {
Assert.state(this.scheduledExecutionTime != null, "No scheduled execution");
this.triggerContext.update(this.scheduledExecutionTime, actualExecutionTime, completionTime);
}
}
- To apply ReschedulingImmediatelyRunnable.java to spring-context, I copy ReshdeulingImmediatelyTaskScheduler.java from org.springframework.scheduling.concurrent.ConcurrentTaskScheduler and changed in
@Override
@Nullable
public ScheduledFuture<?> schedule(Runnable task, Trigger trigger) {
try {
ErrorHandler errorHandler =
(this.errorHandler != null ? this.errorHandler : TaskUtils.getDefaultErrorHandler(true));
return new ReschedulingImmediatelyRunnable(task, trigger, this.scheduledExecutor, errorHandler).schedule();
} catch (RejectedExecutionException ex) {
throw new TaskRejectedException("Executor [" + this.scheduledExecutor + "] did not accept task: " + task, ex);
}
}
- And finally
@EnableScheduling
@Configuration
public class ScheduleConfig implements SchedulingConfigurer {
private static final Logger log = LoggerFactory.getLogger(ScheduleConfig.class);
@Autowired
private Scheduler scheduler;
@Override
public void configureTasks(ScheduledTaskRegistrar taskRegistrar) {
taskRegistrar.setScheduler(new ReshdeulingImmediatelyTaskScheduler(taskExecutor()));
/**
* this task would took much time than expected, and it has to run every hour
*/
taskRegistrar.addTriggerTask(() ->
scheduler.syncPlayStatistics()
, new BaseOnLSETCronTrigger("0 20 * * * ? "));
}
@Bean(destroyMethod = "shutdown")
public ScheduledExecutorService taskExecutor() {
ScheduledThreadPoolExecutor executorService = (ScheduledThreadPoolExecutor) Executors.newScheduledThreadPool(4);
log.info("executorService:{}", executorService);
return executorService;
}
}
Comment From: jhoeller
This might be a good fit with SimpleAsyncTaskScheduler
(#30956) which dispatches each cron-triggered task onto a separate thread, primarily targeting a Virtual Threads setup. Beyond that, we do not intend to support overlapping executions specifically.