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]
