1

An ExecutorService bean does not call the shutdown method on Java 19 with SpringBoot (2.7.12 or 3.1.0). This results in @SpringBootTest tests never completing.

This only happens when you have a custom ExecutorService bean, implements the SchedulingConfigurer and includes the @Scheduled annotation.

The @Bean JavaDoc states that the destroyMethod is inferred, and shutdown is one of the options.

The following code breaks

import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.scheduling.annotation.EnableScheduling;
import org.springframework.scheduling.annotation.Scheduled;
import org.springframework.scheduling.annotation.SchedulingConfigurer;
import org.springframework.scheduling.config.ScheduledTaskRegistrar;

import java.util.concurrent.Executor;
import java.util.concurrent.Executors;

@Configuration
@EnableScheduling
public class ScheduleConfig implements SchedulingConfigurer {
    @Override
    public void configureTasks(ScheduledTaskRegistrar taskRegistrar) {
        taskRegistrar.setScheduler(taskExecutor());
    }

    @Bean
    public Executor taskExecutor() {
        return Executors.newScheduledThreadPool(10);
    }

    /**
     * Works if @Scheduled is not included.
     */
    @Scheduled(cron = "0 0 1 * * *")
    protected void schedule() {

    }
}

Test never completes.

import org.junit.jupiter.api.Test;
import org.springframework.boot.test.context.SpringBootTest;

@SpringBootTest(
        classes =
                {ScheduleConfig.class})
class TestIT {

    @Test
    void test() {
        //NOOP
    }
}

A workaround is to explicitly set the destroyMethod like

 @Bean(destroyMethod = "shutdown")
    public Executor taskExecutor() {
        return Executors.newScheduledThreadPool(10);
    }
Holger
  • 285,553
  • 42
  • 434
  • 765
Suniram
  • 11
  • 1

1 Answers1

0

This is because jdk 19 start implementing AutoClosable interface

DisposableBeanAdapter will invoke close by default for bean implementing AutoClosable

Referring to the method close, it will block until terminated, and as your cron expression is set to invoke at 1 o'clock, it will terminate when the scheduled job finished at 1 o'clock, that's why it seems never completed. You may try * * * * * * and it will finish immediately.

    @Override
    default void close() {
        boolean terminated = isTerminated();
        if (!terminated) {
            shutdown();
            boolean interrupted = false;
            while (!terminated) {
                try {
                    terminated = awaitTermination(1L, TimeUnit.DAYS);
                } catch (InterruptedException e) {
                    if (!interrupted) {
                        shutdownNow();
                        interrupted = true;
                    }
                }
            }
            if (interrupted) {
                Thread.currentThread().interrupt();
            }
        }
    }

Related: Why does the ExecutorService interface not implement AutoCloseable?

samabcde
  • 6,988
  • 2
  • 25
  • 41