Affects: \<4.3.25.RELEASE>
Hello
I found a strange Spring behavior. If we implement SchedulingConfigurer interface like this:
@EnableScheduling
@Configuration
public class CoreConfiguration implements SchedulingConfigurer {
@Bean
public TaskScheduler taskScheduler() {
ThreadPoolTaskScheduler scheduler = new ThreadPoolTaskScheduler();
scheduler.setWaitForTasksToCompleteOnShutdown(true);
return scheduler;
}
@Override
public void configureTasks(ScheduledTaskRegistrar taskRegistrar) {
taskRegistrar.setTaskScheduler(taskScheduler());
}
}
register a cron task and call context.close() then java application will wait until next cron task planned time. (Java main thread will wait for worker thread).
It is strange because if we don't manually set TaskScheduler on ScheduledTaskRegistrar context will close fine.
To make it clear I created a repo with demonstration https://github.com/akvone/Spring-cron-bug.
Comment From: akvone
Beans destroy order differs:
- Variant with waiting
22:17:05.246 [main] INFO org.springframework.context.annotation.AnnotationConfigApplicationContext - Closing org.springframework.context.annotation.AnnotationConfigApplicationContext@6842775d: startup date [Sat Aug 31 22:17:04 MSK 2019]; root of context hierarchy
22:17:05.246 [main] DEBUG org.springframework.core.env.PropertySourcesPropertyResolver - Found key 'spring.liveBeansView.mbeanDomain' in PropertySource 'systemProperties' with value of type String
22:17:05.248 [main] DEBUG org.springframework.beans.factory.support.DefaultListableBeanFactory - Returning cached instance of singleton bean 'lifecycleProcessor'
22:17:05.248 [main] DEBUG org.springframework.beans.factory.support.DefaultListableBeanFactory - Destroying singletons in org.springframework.beans.factory.support.DefaultListableBeanFactory@548b7f67: defining beans [org.springframework.context.annotation.internalConfigurationAnnotationProcessor,org.springframework.context.annotation.internalAutowiredAnnotationProcessor,org.springframework.context.annotation.internalRequiredAnnotationProcessor,org.springframework.context.annotation.internalCommonAnnotationProcessor,org.springframework.context.event.internalEventListenerProcessor,org.springframework.context.event.internalEventListenerFactory,coreConfiguration,coreService,org.springframework.scheduling.annotation.SchedulingConfiguration,org.springframework.context.annotation.internalScheduledAnnotationProcessor,taskScheduler]; root of factory hierarchy
22:17:05.249 [main] DEBUG org.springframework.beans.factory.support.DisposableBeanAdapter - Invoking destroy() on bean with name 'taskScheduler'
22:17:05.249 [main] INFO org.springframework.scheduling.concurrent.ThreadPoolTaskScheduler - Shutting down ExecutorService 'taskScheduler'
22:17:05.251 [main] DEBUG org.springframework.beans.factory.support.DisposableBeanAdapter - Invoking destroy() on bean with name 'org.springframework.context.annotation.internalScheduledAnnotationProcessor'
- Variant without waiting
22:15:41.035 [main] INFO org.springframework.context.annotation.AnnotationConfigApplicationContext - Closing org.springframework.context.annotation.AnnotationConfigApplicationContext@1761e840: startup date [Sat Aug 31 22:15:40 MSK 2019]; root of context hierarchy
22:15:41.035 [main] DEBUG org.springframework.core.env.PropertySourcesPropertyResolver - Found key 'spring.liveBeansView.mbeanDomain' in PropertySource 'systemProperties' with value of type String
22:15:41.036 [main] DEBUG org.springframework.beans.factory.support.DefaultListableBeanFactory - Returning cached instance of singleton bean 'lifecycleProcessor'
22:15:41.036 [main] DEBUG org.springframework.beans.factory.support.DefaultListableBeanFactory - Destroying singletons in org.springframework.beans.factory.support.DefaultListableBeanFactory@50c87b21: defining beans [org.springframework.context.annotation.internalConfigurationAnnotationProcessor,org.springframework.context.annotation.internalAutowiredAnnotationProcessor,org.springframework.context.annotation.internalRequiredAnnotationProcessor,org.springframework.context.annotation.internalCommonAnnotationProcessor,org.springframework.context.event.internalEventListenerProcessor,org.springframework.context.event.internalEventListenerFactory,coreConfiguration,coreService,org.springframework.scheduling.annotation.SchedulingConfiguration,org.springframework.context.annotation.internalScheduledAnnotationProcessor,taskScheduler]; root of factory hierarchy
22:15:41.036 [main] DEBUG org.springframework.beans.factory.support.DefaultListableBeanFactory - Retrieved dependent beans for bean 'taskScheduler': [org.springframework.context.annotation.internalScheduledAnnotationProcessor]
22:15:41.036 [main] DEBUG org.springframework.beans.factory.support.DisposableBeanAdapter - Invoking destroy() on bean with name 'org.springframework.context.annotation.internalScheduledAnnotationProcessor'
22:15:41.037 [main] DEBUG org.springframework.beans.factory.support.DisposableBeanAdapter - Invoking destroy() on bean with name 'taskScheduler'
22:15:41.037 [main] INFO org.springframework.scheduling.concurrent.ThreadPoolTaskScheduler - Shutting down ExecutorService 'taskScheduler'
Comment From: stachera
I had the same problem when I refactored the Scheduler configuration from XML to Java.
When a I use the XML configuration below, the problem does not ocorrs.
<task:annotation-driven scheduler="taskScheduler" />
<bean id="taskScheduler" class="org.springframework.scheduling.concurrent.ThreadPoolTaskScheduler" destroy-method="destroy">
<property name="poolSize" value="5" />
<property name="threadNamePrefix" value="bf-task-" />
<property name="waitForTasksToCompleteOnShutdown" value="true" />
<property name="rejectedExecutionHandler">
<bean class="java.util.concurrent.ThreadPoolExecutor$CallerRunsPolicy"/>
</property>
</bean>
But, when I use the Java configuration below...
@Configuration
@EnableScheduling
public class SchedulerConfig implements SchedulingConfigurer {
@Override
public void configureTasks(ScheduledTaskRegistrar taskRegistrar) {
taskRegistrar.setScheduler(taskScheduler());
}
@Bean(destroyMethod = "destroy")
public ThreadPoolTaskScheduler taskScheduler() {
ThreadPoolTaskScheduler executor = new ThreadPoolTaskScheduler();
executor.setPoolSize(5);
executor.setThreadNamePrefix("bf-task-");
executor.setWaitForTasksToCompleteOnShutdown(true);
executor.setRejectedExecutionHandler(new CallerRunsPolicy());
return executor;
}
}
The active threads from the ThreadPoolTaskScheduler keeping running after stop Tomcat, which created some unexpected errors like these:
The web application [...] appears to have started a thread named [bf-task-1] but has failed to stop it. This is very likely to create a memory leak.
To avoid this behavior and after an exhausted debugging section, I created this workaround to ensure that the ScheduleTasks of the ScheduledAnnotationBeanPostProcessor bean could be destroyed before the ExecutorService.
@Configuration
@EnableScheduling
public class SchedulerConfig implements SchedulingConfigurer {
@Override
public void configureTasks(ScheduledTaskRegistrar taskRegistrar) {
taskRegistrar.setScheduler(taskScheduler());
}
@Bean(destroyMethod = "destroy")
public Executor taskScheduler() {
ThreadPoolTaskScheduler executor = new ThreadPoolTaskScheduler() {
private static final long serialVersionUID = 1L;
@Override
public void destroy() {
processor.destroy();
super.destroy();
}
};
executor.setPoolSize(5);
executor.setThreadNamePrefix("bf-task-");
executor.setWaitForTasksToCompleteOnShutdown(true);
executor.setRejectedExecutionHandler(new CallerRunsPolicy());
return executor;
}
@Autowired
private ScheduledAnnotationBeanPostProcessor processor;
}
And it works!
Comment From: snicoll
You have configured the thread pool to wait, which is exactly what it's doing. There's one task in the queue. I don't really understand the confusion about it "working" when taskRegistrar.setTaskScheduler(taskScheduler()); is not invoked. When it is not invoked the taskScheduler is left untouched and has no task pending. Because it has no task pending, it can shutdown immediately.