Skip to content

ScheduledAnnotationBeanPostProcessor: graceful shutdown should not interrupt currently running jobs #31019

Closed
@pf-joao-schmitt

Description

@pf-joao-schmitt

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

References

https://stackoverflow.com/questions/76868362/spring-graceful-shutdown-not-waiting-for-scheduled-tasks-with-thread-sleep

Metadata

Metadata

Assignees

Labels

in: coreIssues in core modules (aop, beans, core, context, expression)status: backportedAn issue that has been backported to maintenance branchestype: bugA general bug

Type

No type

Projects

No projects

Milestone

Relationships

None yet

Development

No branches or pull requests

Issue actions