Hello, We are in the process to upgrade an application from Camel 4.6.0 to Camel 4.9.0 We are using Camel Spring Boot and the associated automatically configured SpringBootPlatformHttpEngine to expose a REST api
We noticed that in 4.9.0 (started in 4.8.x branch apparently), this engine starts the Camel route asynchronously from the receiving WebServer thread, delegating the execution to a ThreadPoolTaskExecutor created by Spring Boot. ( Quick note : as the default ThreadPoolTaskExecutor created by Spring Boot as only 8 threads (configurable), the change is quite radical compared to the number of thread in a classical WebServer thread pool, it would be nice to recommend to users to augment the size of it if they need to, I didn't find much resources about that ) We also noticed that in the following commit, two level of asynchronism as being introduced : https://github.com/apache/camel-spring-boot/commit/794c5a316447d1bc818b5e1a421c8145ba15a473 * org.apache.camel.component.platform.http.springboot.SpringBootPlatformHttpConsumer#service (called by Spring via org.apache.camel.component.platform.http.springboot.CamelRequestHandlerMapping) is wrapped inside a asynchronous-executing proxy (equivalent to Spring @Async) * and then SpringBootPlatformHttpConsumer is creating a CompletableFuture executed asynchronously in the same ThreadPoolTaskExecutor What was the purpose of having those two levels ? Any single of them would have not be enough ? If we are not mistaken, it appears that both are not playing well together, resulting in the usage of two threads of the ThreadPoolTaskExecutor during the execution of the Camel route instead of only one. In particular org.springframework.aop.interceptor.AsyncExecutionInterceptor was not designed to call a method which return a "real asynchronous" CompletableFuture ( I think this spring issue described exactly that : https://github.com/spring-projects/spring-framework/issues/19964 ) More precisely : AsyncExecutionInterceptor is waiting synchronously for the CompletableFuture returned by SpringBootPlatformHttpConsumer#service to complete : https://github.com/spring-projects/spring-framework/blob/main/spring-aop/src/main/java/org/springframework/aop/interceptor/AsyncExecutionInterceptor.java#L114 Please find below a extract of a thread dump just before the end of the single execution of a REST route where we can see that two thread from the Spring ThreadPoolTaskExecutor are occupied : (I customize the thread prefix to "spring-task" to highlight that point ) : - spring-task-2 is finishing the sending of the response after the route execution - spring-task-1 is just waiting for the CompletableFuture executed by spring-task-2 "spring-task-2@13482" tid=0x43 nid=NA runnable java.lang.Thread.State: RUNNABLE at org.apache.camel.component.platform.http.springboot.SpringBootPlatformHttpConsumer.handleService(SpringBootPlatformHttpConsumer.java:133) at org.apache.camel.component.platform.http.springboot.SpringBootPlatformHttpConsumer.lambda$service$0(SpringBootPlatformHttpConsumer.java:84) at org.apache.camel.component.platform.http.springboot.SpringBootPlatformHttpConsumer$$Lambda/0x000001679aa98ed0.run(Unknown Source:-1) at java.util.concurrent.CompletableFuture$AsyncRun.run$$$capture(CompletableFuture.java:1804) at java.util.concurrent.CompletableFuture$AsyncRun.run(CompletableFuture.java:-1) at java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1144) at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:642) at java.lang.Thread.runWith(Thread.java:1596) at java.lang.Thread.run(Thread.java:1583) "spring-task-1@13467" tid=0x42 nid=NA waiting java.lang.Thread.State: WAITING at jdk.internal.misc.Unsafe.park(Unsafe.java:-1) at java.util.concurrent.locks.LockSupport.park(LockSupport.java:221) at java.util.concurrent.CompletableFuture$Signaller.block(CompletableFuture.java:1864) at java.util.concurrent.ForkJoinPool.unmanagedBlock(ForkJoinPool.java:3780) at java.util.concurrent.ForkJoinPool.managedBlock(ForkJoinPool.java:3725) at java.util.concurrent.CompletableFuture.waitingGet(CompletableFuture.java:1898) at java.util.concurrent.CompletableFuture.get(CompletableFuture.java:2072) at org.springframework.aop.interceptor.AsyncExecutionInterceptor.lambda$invoke$0(AsyncExecutionInterceptor.java:115) at org.springframework.aop.interceptor.AsyncExecutionInterceptor$$Lambda/0x000001679aa988a8.call(Unknown Source:-1) at org.springframework.util.concurrent.FutureUtils.lambda$toSupplier$0(FutureUtils.java:74) at org.springframework.util.concurrent.FutureUtils$$Lambda/0x000001679aa98cb8.get(Unknown Source:-1) at java.util.concurrent.CompletableFuture$AsyncSupply.run$$$capture(CompletableFuture.java:1768) at java.util.concurrent.CompletableFuture$AsyncSupply.run(CompletableFuture.java:-1) at java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1144) at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:642) at java.lang.Thread.runWith(Thread.java:1596) at java.lang.Thread.run(Thread.java:1583) Regards, Fabien ARRAULT -- This e-mail, any attachments and the information contained therein ("this message") are confidential and intended solely for the use of the addressee(s). If you have received this message in error please send it back to the sender and delete it. Unauthorized publication, use, dissemination or disclosure of this message, either in whole or in part is strictly prohibited. Ce message électronique et tous les fichiers joints ainsi que les informations contenues dans ce message (ci-après "le message") sont confidentiels et destinés exclusivement à l'usage de la personne à laquelle ils sont adressés. Si vous avez reçu ce message par erreur, merci de le renvoyer à son émetteur et de le détruire. Toute diffusion, publication, totale ou partielle ou divulgation sous quelque forme que ce soit non expressément autorisée de ce message, est interdite.