1. Overview
On shutdown, by default, Spring’s TaskExecutor simply interrupts all running tasks, but it may be nice to instead have it wait for all running tasks to be complete. This gives a chance for each task to take measures to ensure the shutdown is safe.
In this quick tutorial, we’ll learn how to do this more graceful shutdown of a Spring Boot application when it involves tasks executing using thread pools.
2. Simple Example
Let’s consider a simple Spring Boot application. We’ll autowire the default TaskExecutor bean:
@Autowired
private TaskExecutor taskExecutor;
On application startup, let’s execute a 1-minute-long process using a thread from the thread pool:
taskExecutor.execute(() -> {
Thread.sleep(60_000);
});
When a shutdown is initiated, for example, 20 seconds after startup, the thread in the example is interrupted and the application shuts down immediately.
3. Wait for Tasks to Complete
Let’s change the default behavior of task executor by creating a custom ThreadPoolTaskExecutor bean.
This class provides a flag setWaitForTasksToCompleteOnShutdown to prevent interrupting running tasks. Let’s set it to true:
@Bean
public TaskExecutor taskExecutor() {
ThreadPoolTaskExecutor taskExecutor = new ThreadPoolTaskExecutor();
taskExecutor.setCorePoolSize(2);
taskExecutor.setMaxPoolSize(2);
taskExecutor.setWaitForTasksToCompleteOnShutdown(true);
taskExecutor.initialize();
return taskExecutor;
}
And, we’ll rewrite the earlier logic to create 3 threads each executing a 1-minute-long task.
@PostConstruct
public void runTaskOnStartup() {
for (int i = 0; i < 3; i++) {
taskExecutor.execute(() -> {
Thread.sleep(60_000);
});
}
}
Let’s now initiate a shutdown within the first 60 seconds after the startup.
We see that the application shuts down only 120 seconds after startup. The pool size of 2 allows only two simultaneous tasks to execute so the third one is queued up.
Setting the flag ensures that both the currently executing tasks and queued up tasks are completed.
Note that when a shutdown request is received, the task executor closes the queue so that new tasks can’t be added.
4. Max Wait Time Before Termination
Though we’ve configured to wait for ongoing and queued up tasks to complete, Spring continues with the shutdown of the rest of the container. This could release resources needed by our task executor and cause the tasks to fail.
In order to block the shutdown of the rest of the container, we can specify a max wait time on the ThreadPoolTaskExecutor:
taskExecutor.setAwaitTerminationSeconds(30);
This ensures that for the specified time period, the shutdown process at the container level will be blocked.
When we set the setWaitForTasksToCompleteOnShutdown flag to true, we need to specify a significantly higher timeout so that all remaining tasks in the queue are also executed.
5. Conclusion
In this quick tutorial, we saw how to safely shut down a Spring Boot application by configuring the task executor bean to complete the running and submitted tasks until the end. This guarantees that all tasks will have the indicated amount of time to complete their work.
One obvious side effect is that it can also lead to a longer shutdown phase. Therefore, we need to decide whether or not to use it depending on the nature of the application.
As always, the examples from this article are available over on GitHub.