I am using Spring Boot 2.4.3. The documentation about Liveness & Readiness state mentions:

Tasks expected to run during startup should be executed by CommandLineRunner and ApplicationRunner components instead of using Spring component lifecycle callbacks such as @PostConstruct.

And, the documentation about ApplicationRunner mentions:

This contract is well suited for tasks that should run after application startup but before it starts accepting traffic.

However, I find that when I am executing long-running code such as eagerly warming a cache (which involves HTTP requests with retry backoff), if I ask the app to exit it will not interrupt my code in the ApplicationRunner. It does log messages about the SpringContextShutdownHook thread doing various things, but it leaves my code running.

I can handle shutdown by using, for example, a @PreDestroy method to set a volatile boolean flag, but it's unclear if that is a reasonable mechanism, and of course it doesn't help with interrupting the thread. I could hold a reference to the thread which needs to be interrupted, but at that point I start questioning whether it's a good idea.

Also, I find that @Scheduled methods start executing even before the ApplicationRunner code has completed. This is awkward because scheduled code might begin executing before everything has been initialized. I can add another volatile boolean to control this conflict, but it's again additional logic I'd prefer to avoid.

My suggestions are essentially:

  1. The thread running ApplicationRunners should be interrupted when the JVM shuts down (eg. via SIGINT)
  2. @Scheduled methods should not start until after the ApplicationRunners are complete.
  3. If there is any other guidance about how devs should handle long-running startup code and shutdown thereof, additional material in the docs would be nice to have.

Comment From: wilkinsona

The thread running ApplicationRunners should be interrupted when the JVM shuts down (eg. via SIGINT)

They run on the main thread and that isn't something that we'd want to change, particularly as some users may be relying on the current behaviour. If you want the work to be interrupted – and have implemented it in such a way that interrupting the thread on which it's running will cause it to stop – I think you may be better dispatching that work to an executor of some sort. If you used the context's auto-configured ThreadPoolTaskExecutor any running tasks would be interrupted when the context is closed.

@Scheduled methods should not start until after the ApplicationRunners are complete.

If you have long-running startup code that needs to have completed before, for example, @Scheduled methods are called, it doesn't belong in an application runner. Each ApplicationRunner is, by design, called after application context refresh has completed and the calling of @Scheduled methods begin in response to the ContextRefreshedEvent. If you want the initialization to have completed before @Scheduled methods may be called, you may want to do it during bean initialization, or perhaps in response to the ContextRefreshedEvent in a listener with a precedence higher than LOWEST_PRECEDENCE.

If there is any other guidance about how devs should handle long-running startup code and shutdown thereof, additional material in the docs would be nice to have.

If you haven't seen it already, you may be interested in Spring Framework's documentation on lifecycle callbacks and task execution and scheduling.

Comment From: rehevkor5

Thanks for the response!

I think you may be better dispatching that work to an executor of some sort.

I can certainly do that, it's just requires additional code in order to skip all @Scheduled tasks until that startup work has completed. And, I will need a way to coordinate the order in which the initialization work is done across the various beans, perhaps by using a single threaded ExecutorService or such. It seems that my main use of ApplicationRunner is just to set the app state to "unready".

do it during bean initialization

Bean initialization / lifecycle callbacks are specifically recommended against in the documentation quote I gave in my first post. And from what I can tell, they are uninterruptible in the same way as ApplicationRunner is, so they aren't really suitable for long running work. I was hoping that ApplicationRunner could fix the issues I was having with bean initialization/lifecycle callbacks, but it appears that's not the case.