Skip to content

SchedulingConfigurer's ScheduledTaskRegistrar should reliably shut down before TaskScheduler [SPR-15067] #19633

Closed
@spring-projects-issues

Description

@spring-projects-issues

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:

Issue Links:

Metadata

Metadata

Assignees

Labels

in: coreIssues in core modules (aop, beans, core, context, expression)type: enhancementA general enhancement

Type

No type

Projects

No projects

Milestone

Relationships

None yet

Development

No branches or pull requests

Issue actions