Description
Problem
I'm trying to configure Graceful Shutdown for my application when it receives a SIGTERM event, but I'm getting InterruptedExceptions
for Scheduled jobs where threads enter the wait status (due to a HTTP call or just a simple Thread.sleep()
invocation).
Scenario
I created the example below by generating a project on Spring Initalizr with the following parameters:
- Project: Maven
- Language: Java
- Spring Boot: 3.1.2
- Metadata: default values
- Packaging: Jar
- Java: 17
- No dependencies
From there I changed the DemoApplication
class as following:
package com.example.demo;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.scheduling.annotation.EnableScheduling;
@EnableScheduling
@SpringBootApplication
public class DemoApplication {
public static void main(String[] args) {
System.out.println("Application started at PID: " + ProcessHandle.current().pid());
SpringApplication.run(DemoApplication.class, args);
}
}
Besides that, I created a Scheduled
job as per the following:
package com.example.demo;
import org.springframework.scheduling.annotation.Scheduled;
import org.springframework.stereotype.Component;
@Component
public class SampleJob {
private int counter = 0;
@Scheduled(fixedDelay = 1000L)
public void simpleScheduledJob() throws Exception {
counter++;
System.out.println("Starting job " + counter);
waitThatFails();
//waitThatWorks();
System.out.println("Finished job " + counter);
}
private void waitThatFails() throws Exception {
Thread.sleep(5000L);
}
private void waitThatWorks() {
for (int i = 0; i < 20_000; i++) {
for (int h = 0; h < 1_000_000; h++) {
// Make some wait time
}
if (i % 1_000 == 0) {
System.out.print("|");
}
}
System.out.println();
}
}
And finally configured my application.properties
as following:
server.shutdown=graceful
spring.lifecycle.timeout-per-shutdown-phase=60s
spring.task.execution.shutdown.await-termination=true
spring.task.execution.shutdown.await-termination-period=60s
spring.task.scheduling.shutdown.await-termination=true
spring.task.scheduling.shutdown.await-termination-period=60s
Expectations
When running the code above calling the method waitThatFails
I expect NOT to get the following exception:
java.lang.InterruptedException: sleep interrupted
at java.base/java.lang.Thread.sleep(Native Method) ~[na:na]
at com.example.demo.SampleJob.waitThatFails(SampleJob.java:22) ~[classes/:na]
at com.example.demo.SampleJob.simpleScheduledJob(SampleJob.java:15) ~[classes/:na]
at java.base/jdk.internal.reflect.NativeMethodAccessorImpl.invoke0(Native Method) ~[na:na]
at java.base/jdk.internal.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:77) ~[na:na]
at java.base/jdk.internal.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43) ~[na:na]
at java.base/java.lang.reflect.Method.invoke(Method.java:568) ~[na:na]
at org.springframework.scheduling.support.ScheduledMethodRunnable.run(ScheduledMethodRunnable.java:84) ~[spring-context-6.0.11.jar:6.0.11]
at org.springframework.scheduling.support.DelegatingErrorHandlingRunnable.run(DelegatingErrorHandlingRunnable.java:54) ~[spring-context-6.0.11.jar:6.0.11]
at java.base/java.util.concurrent.Executors$RunnableAdapter.call(Executors.java:539) ~[na:na]
at java.base/java.util.concurrent.FutureTask.runAndReset$$$capture(FutureTask.java:305) ~[na:na]
at java.base/java.util.concurrent.FutureTask.runAndReset(FutureTask.java) ~[na:na]
at java.base/java.util.concurrent.ScheduledThreadPoolExecutor$ScheduledFutureTask.run(ScheduledThreadPoolExecutor.java:305) ~[na:na]
at java.base/java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1136) ~[na:na]
at java.base/java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:635) ~[na:na]
at java.base/java.lang.Thread.run(Thread.java:833) ~[na:na]
and I expect it to work the same way when I call instead the method waitThatWorks
, meaning, the current running job is finished on the event of a kill -15 <PID>
and no next jobs are scheduled to execute in sequence, making it safe to tear down the application.
Interesting to note that for standard Java applications this behavior can be achieved, please see the following example: https://github.com/schmittjoaopedro/schmittjoaopedro.github.io/blob/gh-pages/assets/other/SOQuestionGracefulShutdown.java