Hi!

I think there might be a regression problem in camel-platform-http-starter 
introduced in 4.9.0 and 4.8.2 related to using Virtual Threads. If 
spring.threads.virtual.enabled = true, a Camel application that uses 
camel-platform-http-starter fails at startup with

Caused by: java.lang.RuntimeException: No ThreadPoolTaskExecutor configured
        at 
org.apache.camel.component.platform.http.springboot.SpringBootPlatformHttpAutoConfiguration.lambda$springBootPlatformHttpEngine$1(SpringBootPlatformHttpAutoConfiguration.java:54)
 ~[camel-platform-http-starter-4.9.0.jar:4.9.0]
        at java.base/java.util.Optional.orElseThrow(Optional.java:403) ~[na:na]
        at 
org.apache.camel.component.platform.http.springboot.SpringBootPlatformHttpAutoConfiguration.springBootPlatformHttpEngine(SpringBootPlatformHttpAutoConfiguration.java:54)
 ~[camel-platform-http-starter-4.9.0.jar:4.9.0]
        at 
java.base/jdk.internal.reflect.DirectMethodHandleAccessor.invoke(DirectMethodHandleAccessor.java:103)
 ~[na:na]
        at java.base/java.lang.reflect.Method.invoke(Method.java:580) ~[na:na]
        at 
org.springframework.beans.factory.support.SimpleInstantiationStrategy.lambda$instantiate$0(SimpleInstantiationStrategy.java:171)
 ~[spring-beans-6.2.0.jar:6.2.0]
        ... 24 common frames omitted

unless a ThreadPoolTaskExecutor is explicitly configured.
 
The problem seems to be caused by 
https://github.com/apache/camel-spring-boot/pull/1278/commits/cd4deb0eda78054641056eb948a797b0a5a04560
 which now requires a ThreadPoolTaskExecutor to be configured:

  @Bean(name = "platform-http-engine")
    @ConditionalOnMissingBean(PlatformHttpEngine.class)
    public PlatformHttpEngine springBootPlatformHttpEngine(Environment env) {
        Executor executor;

        if (executors != null && !executors.isEmpty()) {
            executor = executors.stream()
                    .filter(e -> e instanceof ThreadPoolTaskExecutor)
                    .findFirst()
                    .orElseThrow(() -> new RuntimeException("No 
ThreadPoolTaskExecutor configured"));
        } else {
            throw new RuntimeException("No Executor configured");
        }
        int port = Integer.parseInt(env.getProperty("server.port", "8080"));
        return new SpringBootPlatformHttpEngine(port, executor);
    }

If using virtual threads, the default auto-configuration of an executor 
(defined in TaskExecutorConfiguration) instead uses a SimpleAsyncTaskExecutor:

class TaskExecutorConfigurations {
    TaskExecutorConfigurations() {
    }

    @Configuration(
        proxyBeanMethods = false
    )
    @ConditionalOnMissingBean({Executor.class})
    static class TaskExecutorConfiguration {
        TaskExecutorConfiguration() {
        }

        @Bean(
            name = {"applicationTaskExecutor", "taskExecutor"}
        )
        @ConditionalOnThreading(Threading.VIRTUAL)
        SimpleAsyncTaskExecutor 
applicationTaskExecutorVirtualThreads(SimpleAsyncTaskExecutorBuilder builder) {
            return builder.build();
        }

        @Lazy
        @Bean(
            name = {"applicationTaskExecutor", "taskExecutor"}
        )
        @ConditionalOnThreading(Threading.PLATFORM)
        ThreadPoolTaskExecutor 
applicationTaskExecutor(ThreadPoolTaskExecutorBuilder 
threadPoolTaskExecutorBuilder) {
            return threadPoolTaskExecutorBuilder.build();
        }
    }

Hence no ThreadPoolTaskExecutor instance exists by default, and therefore the 
exception is thrown.

A simple workaround I use know is to explicitly configure a 
ThreadPoolTaskExecutor instance (and instruct it to use virtual threads):

@Configuration
public class VirtualThreadPoolTaskExecutorConfig {

  @Bean
  @ConditionalOnMissingBean(AsyncTaskExecutor.class)
  ThreadPoolTaskExecutor threadPoolTaskExecutor(ThreadPoolTaskExecutorBuilder 
builder) {
    ThreadPoolTaskExecutor threadPoolTaskExecutor = builder.build();
     threadPoolTaskExecutor.setVirtualThreads(true);
    return threadPoolTaskExecutor;
  }

}

But I think the original problem should be fixed, for instance by accepting 
both a ThreadPoolTaskExecutor or a SimpleAsyncTaskExecutor in the newly added 
code:

    public PlatformHttpEngine springBootPlatformHttpEngine(Environment env) {
        ...
                    .filter(e -> e instanceof ThreadPoolTaskExecutor || e 
instanceof SimpleAsyncTaskExecutor)
        …

Not pretty, but works.

Have I misunderstood something?

If you agree it is a problem, I’ll submit a ticket and a pull request with 
integration tests triggering the error together with the suggested fix

/Björn

Reply via email to