allthingssecurity opened a new pull request, #27037: URL: https://github.com/apache/camel/pull/27037
# Description [CAMEL-25128](https://issues.apache.org/jira/browse/CAMEL-25128) With a correlation manager that extends `TimeoutCorrelationManagerSupport` (the documented recommendation for custom correlation managers), a request whose write fails is completed twice: once by the write listener and once by the correlation timeout. `NettyProducer.processWithConnectedChannel` registers the state with `putState` (a `TimeoutMap` entry) and writes the request. Two paths can then complete the exchange, and they do not see each other: - write fails first (a connection closed or reset by the server, an encoder that rejects the message): the listener sets the cause and calls `onExceptionCaughtOnce`, which completes the exchange. The `TimeoutMap` entry stays, so `timeout` ms later `onEviction` sets an `ExchangeTimedOutException` on the same exchange and calls the raw `callback.done(false)` again. No timing window is needed for this order. - timeout first (a large request still in the outbound buffer): `onEviction` completes the exchange without setting the state's "callback called" flag, so when the write then fails, `onExceptionCaughtOnce` completes it again. On a route this runs the exchange's on completions as `onComplete` and then `onFailure`, fires `ExchangeCompleted` and then `ExchangeFailed`, and leaves the inflight count at -1. The reply path is safe (`getState` removes the entry, atomically with the eviction), and so is the default `DefaultNettyCamelStateCorrelationManager`, whose completions all run on the channel's event loop through `callbackDoneOnce`. This change: - `NettyCamelState.markDone()`: a compare-and-set on the flag that `callbackDoneOnce` already uses; `callbackDoneOnce` uses it. - `NettyCamelState.onExceptionCaughtOnce(doneSync, cause)`: sets the cause (or the generic `IOException`) only after it has claimed the exchange. The existing `onExceptionCaughtOnce(doneSync)` is kept and delegates to it. - `NettyProducer`: the write listener passes the cause instead of setting it on the exchange first, so a completed exchange is not changed. - `TimeoutCorrelationManagerSupport.onEviction`: sets the timeout and calls the callback only if it claimed the exchange. - The correlation entry of a failed write is still removed at the timeout, as today; the claim makes that eviction a no-op. Tests: - New `NettyTimeoutCorrelationManagerWriteFailureTest`: a `TimeoutCorrelationManagerSupport` subclass (timeout 200 ms) with a worker pool that counts the processed timeouts, and an outbound handler that fails the write either at once, or when the test fails the pending write after the timeout was processed. No sleeps. Each test counts the callbacks of the producer and checks the exception on the exchange. - Without the change both tests fail: `[IOException: Simulated write failure, ExchangeTimedOutException]` and `[ExchangeTimedOutException, IOException: Simulated connection reset]`. - With the change, all camel-netty tests: 130 tests, 0 failures, 0 errors, 9 skipped. The netty hunks of the open #22358 (getOut to getMessage) still apply on top of this change. Found with a TLA+ model of the write, the write listener, the eviction and the reply, which finds both orders and holds with the claim. I then reproduced both with the real producer against a Netty server on localhost, including the negative inflight count on a route. # Target - [x] I checked that the commit is targeting the correct branch (Camel 4 uses the `main` branch) # Tracking - [x] If this is a large change, bug fix, or code improvement, I checked there is a [JIRA issue](https://issues.apache.org/jira/browse/CAMEL) filed for the change (usually before you start working on it). # Apache Camel coding standards and style - [x] I checked that each commit in the pull request has a meaningful subject line and body. - [ ] I have run `mvn clean install -DskipTests` locally from root folder and I have committed all auto-generated changes. (I built and tested the affected module, including the formatter and import-sort plugins. I did not run the full root build.) # AI-assisted contributions - [x] If this PR includes AI-generated code, commits have proper co-authorship attribution (e.g., `Co-authored-by` trailers) and the PR description identifies the AI tool used. This PR was prepared with Claude Code (Claude Opus 5.5). The commit carries a `Co-Authored-By` trailer. _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]
