Thanks for your quick response Federico. As you ask for my suggestions, I will try to share my (partial) understanding. Please excuse me if some remarks are not accurate or lack backgrounds on how Camel works or on what it seeks to achieve.
Like this, I would say that to fix the double thread consumption, dropping the use of AsyncExecutionInterceptor and only keep the CompletableFuture.runAsync in an executor as it is currently in org.apache.camel.component.platform.http.springboot.SpringBootPlatformHttpConsumer#service would be enough ; as Spring MVC (unlike @Async) knows how to handle CompletableFuture efficiently (org.springframework.web.servlet.mvc.method.annotation.DeferredResultMethodReturnValueHandler#handleReturnValue) However, this may not be enough to behave like "async Spring Boot rest service". By the way, what exactly is your reference for "async Spring Boot rest service" ? - the support of Async response type in MVC controller methods ? ( as in https://docs.spring.io/spring-framework/reference/web/webmvc/mvc-ann-async.html ) - or maybe full Reactive Webflux ? That being said, I guess that both cases are quite equivalent in term of this discussion : they don't block any upstream threads And I think, that crucially they don't either block any other threads *by themselves* : I am saying that because currently in Camel 4.9.0 : the thread that is executing org.apache.camel.component.platform.http.springboot.SpringBootPlatformHttpConsumer#handleService will never be freed until the exchange is completely process (whatever the way this exchange will be processed) In this respect, the previous situation was easier to reason about, and potentially not that worst : - before it was a thread of the WebServer pool ( default in Tomcat under Spring Boot is 200 threads ) and indeed : if all the thread of the WebServer pools are buzy : => the server will begin to queue request and become irresponsive (for all servlet) but it seems to me to be a classical and well documented behaviour (at least in the old synchronous world) - now it is a thread of the ThreadPoolTaskExecutor (the one provided by Spring Boot defaulting to only 8 threads ) : In case of heavy load, ( depending on the profile/need of the application) this previous "problem" seems to me to be only shift to the ThreadPoolTaskExecutor ( but, indeed, only limited to the Camel entrypoint, or more accurately, to all the tasks using the ThreadPoolTaskExecutor of Spring Boot) and with only 8 threads by default => it will queue request much more rapidly if not tuned ( and potentially add a lot of latency ) If the Camel route is fully synchronous => in both case, the thread executing SpringBootPlatformHttpConsumer#handleService will be used to execute the whole route : so that fine, one thread is needed after all. But if the camel route processing switch threads (which is our case because we split the processing of the messages) => this calling thread will stay blocked until the end of the processing, which in my opinion would not be as best as a well written async Spring Boot rest service. So to be equivalent, to the async web support of Spring, my guess would be that SpringBootPlatformHttpConsumer#handleService would need to go fully reactive and to be able to release the calling thread when the route switches thread. Hoping this was a little useful, Regards, Fabien On Fri, 24 Jan 2025 at 18:02, Federico Mariani < federico.mariani.1...@gmail.com> wrote: > Hello Fabien, > > Good point, I refactored the platform-http component because it was > behaving differently compared to an async Spring Boot rest service. In > particular, before that commit, the component was not using the Spring Boot > async thread pool (like async spring boot does) but the sync thread was > being used, therefore, before that commit the component was not async at > all, and in some cases (under some load) the server stopped working. > > With the commit you mentioned, I wanted the Camel Http Platform to be as > close as possible to an Async Spring Boot service from a threading > perspective and, of course, avoid the locks. > I didn't notice the creation of multiple tasks, thanks for reporting it, > I'll have a look for sure, by any chance do you have any suggestions on how > to solve this issue? IIRC there are tests for the issue we faced with the > lock, and some assertions regarding the asynchronicity. > > Regards, > Federico > > Il giorno ven 24 gen 2025 alle ore 15:50 Fabien Arrault < > farra...@consulting-for.d-edge.com> ha scritto: > > > 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. > > > -- Fabien ARRAULT (consultant) <https://data.sigilium.com/signatures/rcgsp-rf-vea1joicbfrswihswyk8rc/link?element=name> Architect - Move2Cloud IRMA and OTA - Consultant <https://data.sigilium.com/signatures/rcgsp-rf-vea1joicbfrswihswyk8rc/link?element=job> [image: Map-marker] 66 rue des archives, 75003 Paris [image: Linkedin] <https://www.linkedin.com/company/d-edge-hospitality-solutions/> [image: Twitter] <https://twitter.com/D_EDGE_Hosp> [image: Facebook] <https://www.facebook.com/D.EDGE.Hospitality.Solutions/> [image: Instagram] <https://www.instagram.com/d_edge_hospitality/> [image: Click to know more] <https://data.sigilium.com/signatures/rcgsp-rf-vea1joicbfrswihswyk8rc/redirect_link> -- 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.