When using virtual threads all @Scheduled
executors stop working after first exception on any @Scheduled
executor and no error is logged. With disabled virtual threads @Scheduled
executors continue working and exception is logged with TaskUtils$LoggingErrorHandler
and SimpleAsyncUncaughtExceptionHandler
.
Demo is here: https://github.com/ztomic/scheduling-demo
In this demo DemoEvent
is generated every 5 seconds with @Scheduled
task, and there are two listeners, one is @Async
and other is synchronous and both of them are throwing exception on every second invocation. Also, there is one @Scheduled
task which throws exception on every second invocation.
With disabled virtual threads, all @Scheduled
tasks continue working after exception.
Run application with virtual threads disabled and after that with virtual threads enabled in application.yml
.
Comment From: ztomic
Also ThreadPoolTaskSchedulerCustomizer
is not used with virtual threads.
@Bean
public ThreadPoolTaskSchedulerCustomizer threadPoolTaskSchedulerCustomizer() {
return taskScheduler -> {
taskScheduler.setErrorHandler(t -> LoggerFactory.getLogger(ThreadPoolTaskScheduler.class).error("Unexpected error occurred in {}scheduled task.", Thread.currentThread().isVirtual() ? "virtual " : "", t));
};
}
Comment From: wilkinsona
When using virtual threads all
@Scheduled
executors stop working after first exception on any@Scheduled
executor and no error is logged. With disabled virtual threads@Scheduled
executors continue working and exception is logged withTaskUtils$LoggingErrorHandler
andSimpleAsyncUncaughtExceptionHandler
Thanks for the report and sample project. The difference in behaviour is due to differences in Spring Framework's ThreadPoolTaskScheduler
(used with platform threads) and SimpleAsyncTaskScheduler
(used with virtual threads). The latter does not have an ErrorHandler
and therefore does not decorate the scheduled tasks with the error handler.
I don't think there's anything we can do about this in Spring Boot as there's no way that I am aware of for us to combine the simple non-pooling behaviour of SimpleAsyncTaskScheduler
with the error handling behaviour of ThreadPoolTaskScheduler
. We'll transfer this issue to the Framework team for their consideration.
ThreadPoolTaskSchedulerCustomizer
is not used with virtual threads
This is to be expected as there's no ThreadPoolTaskScheduler
and no pooling in general when using virtual threads.
Comment From: mhalbritter
Also ThreadPoolTaskSchedulerCustomizer is not used with virtual threads.
If you use virtual threads, SimpleAsyncTaskScheduler
is doing the scheduling. You can customize the scheduler with SimpleAsyncTaskSchedulerCustomizer
.
If you use platform threads, ThreadPoolTaskScheduler
is doing the scheduling. You can customize the scheduler with ThreadPoolTaskSchedulerCustomizer
.
Comment From: ztomic
Ok, thanks for clarifications. Maybe it should be mentioned in release notes or documentation (if not already), so users are aware that if they enable virtual threads they should also handle all exceptions in @Async
and @Scheduled
methods to avoid this behaviour.
Comment From: jhoeller
I suppose this problem specifically materializes with fixed-delay tasks which we need to execute on the main scheduler thread... and which we therefore need to wrap in a defensive error handler.
Comment From: ztomic
I have tested with 6.1.2-SNAPSHOT
and it seems that now is working ok.
Comment From: jhoeller
Good to hear! Thanks for testing the snapshot.
Comment From: kSzajo
Also ThreadPoolTaskSchedulerCustomizer is not used with virtual threads.
If you use virtual threads,
SimpleAsyncTaskScheduler
is doing the scheduling. You can customize the scheduler withSimpleAsyncTaskSchedulerCustomizer
.If you use platform threads,
ThreadPoolTaskScheduler
is doing the scheduling. You can customize the scheduler withThreadPoolTaskSchedulerCustomizer
.
ThreadPoolTaskSchedulerCustomizer
does not contain setErrorHandler
Is it possible to declare exception handler for SimpleAsyncTaskScheduler
?