Use case:

During Graceful Shutdown, we rely on scheduled tasks being executed until final shutdown. This works with springs v6.0.x and stopped working in v6.1

With this change all scheduled tasks are canceled and continueExistingPeriodicTasksAfterShutdownPolicy of the ThreadPoolTaskScheduler has no effect here.

Here is a spring boot sample application:

@SpringBootApplication
@EnableScheduling
public class DemoApplication implements CommandLineRunner {

    public static void main(String[] args) {
        SpringApplication.run(DemoApplication.class, args);
    }

    @Override
    public void run(String... args) throws Exception {
        System.out.println("Sleeping forever, you can press Ctrl+C to shutdown application");
        while (true) {
            Thread.sleep(1000);
        }
    }

    @Bean
    ThreadPoolTaskSchedulerCustomizer threadPoolTaskSchedulerPhaseCustomizer() {
        return p -> {
            p.setPhase(0);
            p.setContinueExistingPeriodicTasksAfterShutdownPolicy(true);
            p.setWaitForTasksToCompleteOnShutdown(true);
            p.setAcceptTasksAfterContextClose(true);
            p.setExecuteExistingDelayedTasksAfterShutdownPolicy(true);
            p.setAwaitTerminationSeconds(180);
            p.setPoolSize(4);
            p.setThreadNamePrefix("custom-scheduler-");
        };
    }


    @Scheduled(fixedRate = 1000, scheduler = ScheduledAnnotationBeanPostProcessor.DEFAULT_TASK_SCHEDULER_BEAN_NAME)
    public void test() {
        var pid = System.getProperty("PID");
        System.out.println("Kill me with 'kill -int " + pid + "' - " + Thread.currentThread().getName() + " - " + System.currentTimeMillis());
    }

    @Bean
    SmartLifecycle waitOnShutdown() {
        return new SmartLifecycle() {
            private boolean running;

            @Override
            public void start() {
                this.running = true;
            }

            @Override
            public void stop() {
                var counter = 0;
                System.out.println("Waiting for 10 seconds before stopping");
                do {
                    try {
                        System.out.println("Waiting for 1 second");
                        Thread.sleep(1000);
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                } while (counter++ < 10);
                this.running = true;
            }

            @Override
            public boolean isRunning() {
                return this.running;
            }
        };
    }
}

After sending kill -int xyz to the output of the scheduled task stops.

Our current workaround is to use a custom ScheduledAnnotationBeanPostProcessor ignoring the ContextClosedEvent

Comment From: jhoeller

It looks like the fix for #24629 is too harsh in assuming that all declaratively scheduled tasks should be cancelled unconditionally on context close. I'll see how we can revise this while still adhering to the target scenario in #24629 as well.

Comment From: jhoeller

The revision will be available in the upcoming 6.1.9 snapshot, reducing the early cancellation to non-managed objects while letting regular singleton beans participate in the regular factory destruction arrangement and the scheduler setup. Please give it an early try if you have the chance today, in advance of the actual 6.1.9 release tomorrow...

Comment From: ansgarschulte

Thx for the fast fix and turnaround. I can confirm that it is working with my test application and setting continueExistingPeriodicTasksAfterShutdownPolicy to false (default value).

Comment From: jhoeller

Good to hear, thanks for the immediate feedback!