spring-projects / spring-boot

Spring Boot
https://spring.io/projects/spring-boot
Apache License 2.0
73.72k stars 40.34k forks source link

When virtual threads are enabled, configure Spring Integration's task scheduler to use them #41188

Open csh0034 opened 3 weeks ago

csh0034 commented 3 weeks ago

In the PR below, WebSocketMessagingAutoConfiguration has been modified to use a virtual thread executor

https://github.com/spring-projects/spring-boot/pull/39611

public class WebSocketMessagingAutoConfiguration {
  // ...
  WebSocketMessageConverterConfiguration(ObjectMapper objectMapper,
    Map<String, AsyncTaskExecutor> taskExecutors) {
    this.objectMapper = objectMapper;
    this.executor = determineAsyncTaskExecutor(taskExecutors);
  }

  private static AsyncTaskExecutor determineAsyncTaskExecutor(Map<String, AsyncTaskExecutor> taskExecutors) {
    if (taskExecutors.size() == 1) {
      return taskExecutors.values().iterator().next();
    }
    return taskExecutors.get(TaskExecutionAutoConfiguration.APPLICATION_TASK_EXECUTOR_BEAN_NAME);
  }

  // ...
}

Upon checking the taskExecutors being injected, it is taskScheduler.
When used with spring-integration, the following Bean is being configured first.
Is it possible to modify it to allow for auto-configuration?

@Configuration(proxyBeanMethods = false)
@ConditionalOnBean(TaskSchedulerBuilder.class)
@ConditionalOnMissingBean(name = IntegrationContextUtils.TASK_SCHEDULER_BEAN_NAME)
@SuppressWarnings("removal")
public class IntegrationAutoConfiguration {
  // ..
  protected static class IntegrationTaskSchedulerConfiguration {
    @Bean(name = IntegrationContextUtils.TASK_SCHEDULER_BEAN_NAME)
    public ThreadPoolTaskScheduler taskScheduler(TaskSchedulerBuilder taskSchedulerBuilder,
      ObjectProvider<ThreadPoolTaskSchedulerBuilder> threadPoolTaskSchedulerBuilderProvider) {
      // ...
    }
  }
  // ..
}

Below is the configuration added without using auto-configuration.

@Configuration
class ExecutorConfig {
  @Bean(
    name = [
      TaskExecutionAutoConfiguration.APPLICATION_TASK_EXECUTOR_BEAN_NAME,
      AsyncAnnotationBeanPostProcessor.DEFAULT_TASK_EXECUTOR_BEAN_NAME
    ]
  )
  fun applicationTaskExecutorVirtualThreads(builder: SimpleAsyncTaskExecutorBuilder): SimpleAsyncTaskExecutor {
    return builder.build()
  }
}

Of course, the bottom properties are also set.

spring:
  threads:
    virtual:
      enabled: true
mhalbritter commented 3 weeks ago

Ah, hm, IntegrationAutoConfiguration.IntegrationTaskSchedulerConfiguration#taskScheduler is executed if there's no taskScheduler bean. TaskSchedulingConfigurations.TaskSchedulerConfiguration#taskSchedulerVirtualThreads creates such a bean, but only if @EnableScheduling has been used on the main class.

So I guess we should make IntegrationTaskSchedulerConfiguration virtual threads aware.

The workaround for now is to add @EnableScheduling on the main class (or any other @Configuration class).

csh0034 commented 3 weeks ago

@mhalbritter I understand that IntegrationTaskSchedulerConfiguration needs to recognize virtual threads.

However, even when adding the following configuration, IntegrationTaskSchedulerConfiguration is called before TaskSchedulerConfiguration, and TaskSchedulingConfigurations.TaskSchedulerConfiguration#taskSchedulerVirtualThreads is not called.

@Configuration
@EnableScheduling
class ScheduleConfig

Is there a priority issue?

Additionally, as mentioned in the initial question, the executor assigned to WebSocketMessageConverterConfigurationshould be TaskSchedulerConfiguration.TaskSchedulerConfiguration#taskSchedulerVirtualThreads, but this method is not called at all. WebSocketMessageConverterConfiguration.determineAsyncTaskExecutor only has the taskScheduler created in IntegrationAutoConfiguration.IntegrationTaskSchedulerConfiguration#taskScheduler

https://github.com/spring-projects/spring-boot/pull/39611/commits/e9bf2b32cf019a7c0f4854334d2f1a12e0f8d4a8

Should I provide a sample for verification?

mhalbritter commented 3 weeks ago

Should I provide a sample for verification?

Yes, please.

csh0034 commented 3 weeks ago

websocket-sample.zip

p.s. Even though I'm not using SockJS, I noticed that defaultSockJsSchedulerContainer is added in WebSocketMessageConverterConfiguration.determineAsyncTaskExecutor

It wasn't there in version 3.3.0, but it appears in version 3.3.1. I don't understand the reason for this change.

csh0034 commented 2 weeks ago

It seems that the reason TaskSchedulingConfigurations.TaskSchedulerConfiguration#taskSchedulerVirtualThreads is not being called is due to AbstractMessageBrokerConfiguration#messageBrokerTaskScheduler.

TaskSchedulingConfigurations.TaskSchedulerConfiguration:
      Did not match:
         - @ConditionalOnMissingBean (types: org.springframework.scheduling.TaskScheduler,java.util.concurrent.ScheduledExecutorService; SearchStrategy: all) found beans of type 'org.springframework.scheduling.TaskScheduler' messageBrokerTaskScheduler (OnBeanCondition)
csh0034 commented 2 weeks ago

The AbstractMessageBrokerConfiguration in Spring has been modified to use the executor registered in ChannelRegistration, and WebSocketMessagingAutoConfiguration in Spring Boot has been changed to inject the auto-configured executor into ChannelRegistration.

However, due to the various Beans declared in Spring's AbstractMessageBrokerConfiguration, TaskExecutorConfigurations.TaskExecutorConfiguration in Spring Boot is not called.

TaskExecutorConfigurations.TaskExecutorConfiguration:
      Did not match:
         - @ConditionalOnMissingBean (types: java.util.concurrent.Executor; SearchStrategy: all) found beans of type 'java.util.concurrent.Executor' clientInboundChannelExecutor, clientOutboundChannelExecutor, brokerChannelExecutor (OnBeanCondition)

I'm adding this comment here because I'm unsure where to raise this issue.