Description
Ruslan Stelmachenko opened SPR-15067 and commented
When I define taskScheduler
bean with setWaitForTasksToCompleteOnShutdown(true)
and setAwaitTerminationSeconds(20)
and at the same time schedule some tasks using SchedulingConfigurer
and it's ScheduledTaskRegistrar
, then taskScheduler
never shutdown gracefully because it's queue contains programmatically scheduled tasks (using taskRegistrar
), but no one cancels these tasks before taskScheduler
tries to shutdown.
@SpringBootApplication
@EnableScheduling
public class SchedulingBugDemoApplication implements SchedulingConfigurer {
private static final Logger log = LoggerFactory.getLogger(SchedulingBugDemoApplication.class);
public static void main(String[] args) {
SpringApplication.run(SchedulingBugDemoApplication.class, args);
}
@Bean
public TaskScheduler taskScheduler() {
ThreadPoolTaskScheduler scheduler = new ThreadPoolTaskScheduler();
scheduler.setThreadNamePrefix("myScheduler-");
scheduler.setPoolSize(10);
// block spring context stopping to allow SI pollers to complete
// (to graceful shutdown still running tasks, without destroying beans used in these tasks)
scheduler.setWaitForTasksToCompleteOnShutdown(true);
scheduler.setAwaitTerminationSeconds(20);
return scheduler;
}
@Override
public void configureTasks(ScheduledTaskRegistrar taskRegistrar) {
taskRegistrar.addTriggerTask(
() -> work(),
new CronTrigger("*/5 * * * * *"));
// The second task sits in the scheduler queue and prevents scheduler to shutdown().
// The task's cancellation lies in taskRegistrar's destroy() method, but no one
// calls it until context bean destruction is in place. At the same time the context
// waits for taskScheduler to terminate to continue beans destruction procedure.
taskRegistrar.addTriggerTask(
() -> work(),
new CronTrigger("0 0 0 */1 * *"));
}
void work() {
log.info("Working...");
}
}
The second task sits in the scheduler queue and prevents scheduler to shutdown()
.
The task's cancellation lies in taskRegistrar
's destroy()
method, but no one calls it until context bean destruction is in place. At the same time the context waits for taskScheduler
to terminate to continue beans destruction procedure.
When we use @Scheduled
annotation, then this problem is not present because ScheduledAnnotationBeanPostProcessor
's destroy()
method explicitly calls ScheduledTaskRegistrar
's destroy()
method, which cancels all the scheduled tasks.
But when we register our tasks programmatically, no one calls ScheduledTaskRegistrar
's destroy()
method before TaskScheduler
tries to shutdown()
and awaitTermination()
.
Maybe we can force somehow to call ScheduledTaskRegistrar
's destroy()
method before TaskScheduler
's destroy()
method? Of course this can be done manually for example:
private volatile ScheduledTaskRegistrar taskRegistrar;
@Override
public void configureTasks(ScheduledTaskRegistrar taskRegistrar) {
this.taskRegistrar = taskRegistrar;
// ...
}
@PreDestroy
public void destroy() {
taskRegistrar.destroy();
}
This workaroud works because the @Configuration
bean's destory()
method called before TaskScheduler
's. But I don't know if it is guaranteed or not.
I can imagine many workarounds for this but will be better to somehow make it work out of the box, as the @Scheduled
annotation works.
Best regards, Ruslan.
Affects: 4.3.5
Attachments:
- scheduling-bug-demo.zip (61.47 kB)
Issue Links:
- findDefaultEntityManagerFactory should consider EMF bean's primary flag [SPR-7549] #12206 findDefaultEntityManagerFactory should consider EMF bean's primary flag
- ScheduledAnnotationBeanPostProcessor should reliably apply after AnnotationAwareAspectJAutoProxyCreator [SPR-14692] #19256 ScheduledAnnotationBeanPostProcessor should reliably apply after AnnotationAwareAspectJAutoProxyCreator
- AsyncAnnotationBeanPostProcessor could find TaskExecutor by type/name [SPR-13248] #17839 AsyncAnnotationBeanPostProcessor could find TaskExecutor by type/name
- Track bean dependencies for calls between @Bean methods within @Configuration classes [SPR-15069] #19635 Track bean dependencies for calls between
@Bean
methods within@Configuration
classes