[ 
https://issues.apache.org/jira/browse/CAMEL-26578?page=com.atlassian.jira.plugin.system.issuetabpanels:all-tabpanel
 ]

Work on CAMEL-26578 started by shashank.
----------------------------------------
> camel-core - ProducerTemplate: the future of an async send or request never 
> completes when the template is stopped before the task ran
> --------------------------------------------------------------------------------------------------------------------------------------
>
>                 Key: CAMEL-26578
>                 URL: https://issues.apache.org/jira/browse/CAMEL-26578
>             Project: Camel
>          Issue Type: Bug
>          Components: camel-core
>            Reporter: shashank
>            Assignee: shashank
>            Priority: Major
>
> Found with Fray (a controlled concurrency testing tool for the JVM) on a test 
> that stops a {{ProducerTemplate}} while an {{asyncRequestBody}} is pending; 
> it failed at the third schedule, and the latch-based JUnit tests below 
> reproduce it deterministically.
> {{DefaultProducerTemplate.asyncSendExchange}} creates the 
> {{CompletableFuture}} it returns and {{submit}}s a lambda to the template 
> executor that calls {{getProducerCache().asyncSendExchange(..., future)}}. 
> Nobody completes that future when the lambda does not get to the producer 
> cache:
> # *The task is still queued when the template stops.* {{doStop}} calls 
> {{ExecutorServiceManager.shutdownNow(executor)}}, which drops the queued 
> tasks; their futures are never completed.
> # *The task runs after the stop* (an executor that does not drop it, such as 
> a custom one set with {{setExecutorService}}): {{getProducerCache()}} throws 
> {{IllegalStateException("ProducerTemplate has not been started")}} inside the 
> lambda, {{submit()}} keeps it in its own {{FutureTask}} which nobody reads, 
> and the returned future is never completed.
> # *Anything else that throws before the producer cache's own try/catch*, e.g. 
> {{Endpoint.createExchange}} failing in 
> {{DefaultProducerCache.asyncSendExchange}}: same as 2.
> A caller in {{future.get()}} without a timeout waits for ever; 
> {{thenApply}}/{{whenComplete}} stages never run. 
> {{DefaultFluentProducerTemplate.asyncSend()}}/{{asyncRequest()}} go through 
> the same method.
> Short trace (case 1): caller {{asyncRequestBody("direct:start", body)}} -> 
> task queued behind a busy executor thread -> another thread 
> {{template.stop()}} -> {{shutdownNow}} returns the task, which is dropped -> 
> {{future.get()}} never returns.
> {code:java}
> ProducerTemplate producer = context.createProducerTemplate();
> ExecutorService single = 
> context.getExecutorServiceManager().newSingleThreadExecutor(this, "test");
> producer.setExecutorService(single);
> single.execute(() -> await(release));            // keep the only thread busy
> Future<Object> f = producer.asyncRequestBody("direct:start", "Hello World");
> producer.stop();                                  // shutdownNow drops the 
> queued task
> release.countDown();
> f.get();                                          // never returns
> {code}
> The same happens with the default executor when the template is stopped while 
> more async requests are queued than the pool has threads. (Not covered here: 
> a template that is not stopped itself, whose pool the ExecutorServiceManager 
> shuts down when the CamelContext stops; its queued tasks are dropped after 
> the shutdown await while the template stays started.)
> Fix idea: send through a small {{Runnable}} that holds the future and 
> completes it exceptionally when sending throws; keep the tasks that have not 
> started in a set and complete their futures exceptionally (with a 
> {{RejectedExecutionException}}) in {{doStop}} after the executor is shut down 
> (also a task handed over while the template is stopping). Sent exchanges and 
> the futures of requests that run are unchanged.
> _Filed with Claude Code on behalf of allthingssecurity._



--
This message was sent by Atlassian Jira
(v10.3.26#10030026)

Reply via email to