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
@Scheduledexecutors stop working after first exception on any@Scheduledexecutor and no error is logged. With disabled virtual threads@Scheduledexecutors continue working and exception is logged withTaskUtils$LoggingErrorHandlerandSimpleAsyncUncaughtExceptionHandler
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.
ThreadPoolTaskSchedulerCustomizeris 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,
SimpleAsyncTaskScheduleris doing the scheduling. You can customize the scheduler withSimpleAsyncTaskSchedulerCustomizer.If you use platform threads,
ThreadPoolTaskScheduleris doing the scheduling. You can customize the scheduler withThreadPoolTaskSchedulerCustomizer.
ThreadPoolTaskSchedulerCustomizerdoes not contain setErrorHandler
Is it possible to declare exception handler for SimpleAsyncTaskScheduler?