allthingssecurity opened a new pull request, #29194:
URL: https://github.com/apache/camel/pull/29194

   # Description
   
   [CAMEL-26578](https://issues.apache.org/jira/browse/CAMEL-26578)
   
   `DefaultProducerTemplate.asyncSendExchange` returns a `CompletableFuture` 
and submits a lambda to the template executor which hands that future to the 
producer cache. When the lambda never gets to the producer cache, nobody 
completes the future and a caller in `future.get()` waits for ever:
   - the task is still queued when the template stops: `doStop` calls 
`shutdownNow` on the executor, which drops it;
   - the task runs after the stop (an executor which does not drop it): 
`getProducerCache()` throws `IllegalStateException` inside the lambda, and 
`submit()` keeps it in a `FutureTask` nobody reads;
   - anything else that throws before the producer cache's own try/catch, such 
as `Endpoint.createExchange`.
   
   `DefaultFluentProducerTemplate.asyncSend()` / `asyncRequest()` go through 
the same method.
   
   Found with Fray (controlled concurrency testing) on a test that stops a 
template while an `asyncRequestBody` is pending.
   
   ## The fix
   
   - The lambda becomes a small `AsyncSendTask` (`Runnable`, passed to 
`execute`). When sending throws, it completes the future exceptionally with 
that exception.
   - The tasks that are handed to the executor and have not started yet are 
kept in a set. `doStop` completes their futures exceptionally with a 
`RejectedExecutionException` after the executor is shut down. This does not 
depend on what `shutdownNow` returns, which an executor may wrap (instrumented 
executors) or not drop at all.
   - A task and the stop claim the task with one `AtomicBoolean`, so a future 
is completed once, and a task the stop rejected does not send when its executor 
runs it later. If the template is stopping while a task is handed over, or its 
executor is already shut down (a caller-runs policy then discards the task 
silently), the caller rejects it itself (the stop may have missed it).
   
   Nothing changes for requests that run: they are sent and completed by the 
producer cache as before. A request the executor rejects at once still throws 
`RejectedExecutionException` to the caller. A future that used to hang is now 
completed exceptionally, so `get()` throws `ExecutionException` (also for 
`asyncSend(endpoint, exchange)`, where a failure while sending is otherwise set 
on the exchange); nothing to migrate, so no upgrade guide entry.
   
   ## Tests
   
   `DefaultProducerTemplateAsyncStopTest` (latches, no sleeps): a request 
queued behind a busy single-thread executor when the template stops (template 
and fluent template), a request whose executor runs the task after the stop (it 
is rejected and nothing is sent), an endpoint whose `createExchange` throws, 
and a control (a request before and after a stop/start completes normally). On 
main the four failing cases time out; with the fix all pass, 20 times in a row.
   
   _Claude Code on behalf of allthingssecurity_
   
   🤖 Generated with [Claude Code](https://claude.com/claude-code)
   


-- 
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.

To unsubscribe, e-mail: [email protected]

For queries about this service, please contact Infrastructure at:
[email protected]

Reply via email to