CAMEL-24435: camel-as2 - clear the per-request asynchronous MDN state on the connection context - #26207
CAMEL-24435: camel-as2 - clear the per-request asynchronous MDN state on the connection context#26207oscerd wants to merge 1 commit into
Conversation
… on the connection context The AS2 request handler creates its HttpContext once, outside the request loop, and reuses it for every request handled on that connection. ResponseMDN sets RECIPIENT_ADDRESS and ASYNCHRONOUS_MDN on it while handling a request that asked for an asynchronous receipt, and nothing removed them afterwards. A later request on the same connection that did not ask for one therefore still found a non-null recipient address, and the handler dispatched a second asynchronous MDN - carrying the report still stored under ASYNCHRONOUS_MDN - to the address supplied by the earlier request. Both attributes are now removed in a finally block after every request, so the clearing also covers the paths that skip the send, such as a request URI with no registered consumer configuration. The same finally clears CURRENT_CONSUMER_CONFIG. setupConfigurationForRequest returns early without setting it when a path has no configuration, so the ThreadLocal had the identical staleness: a request could otherwise pair its own recipient address with the previous request's signing key. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01PEkdQs2n6dsP4SFTcfG8rB Signed-off-by: Andrea Cosentino <ancosen@gmail.com>
gnodet
left a comment
There was a problem hiding this comment.
The fix is correct and the test structure is sound. Two observations below.
Static analysis note: ast-grep flagged an empty catch (IOException ignore) on the serverConnection.close() call in the outer finally block (line 534 in the PR file) and semgrep flagged a plain ServerSocket instantiation at line 448. Both are pre-existing, not introduced by this PR — disregarding.
This review was generated by an AI agent, Hermès, on behalf of @gnodet.
| receipts.expectedMessageCount(1); | ||
| requestBodyAndHeaders("direct://SEND", EDI_MESSAGE, | ||
| as2Headers("http://localhost:" + jettyPort.getPort() + "/handle-receipts")); | ||
| receipts.setResultWaitTime(TimeUnit.SECONDS.toMillis(10)); |
There was a problem hiding this comment.
setResultWaitTime is set after the requestBodyAndHeaders call that triggers the MDN. If the MDN arrives and the latch reaches zero between line 79 and line 80, assertIsSatisfied on line 81 will still work (latch is already at zero, so it returns immediately without waiting), but the wait-time guarantee is meaningless from a correctness standpoint. It cannot cause a false positive here — expectedMessageCount(1) is already satisfied by that point — but the conventional pattern is to configure the endpoint before triggering the action:
| receipts.setResultWaitTime(TimeUnit.SECONDS.toMillis(10)); | |
| receipts.setResultWaitTime(TimeUnit.SECONDS.toMillis(10)); | |
| requestBodyAndHeaders("direct://SEND", EDI_MESSAGE, | |
| as2Headers("http://localhost:" + jettyPort.getPort() + "/handle-receipts")); | |
| receipts.assertIsSatisfied(); |
| } | ||
|
|
||
| @Test | ||
| public void asyncMdnStateDoesNotLeakToTheNextRequestOnTheSameConnection() throws Exception { |
There was a problem hiding this comment.
ℹ️ Test classification: The class name ends in Test, so it runs under maven-surefire as a unit test. The parent POM excludes **/*IT.java from surefire and routes those to failsafe. Other AS2 async MDN tests (AS2AsyncMDNServerManagerIT) use the IT suffix. Since this test is self-contained (sets up its own AS2ServerConnection in setupResources), running under surefire is fine — the test does not require the failsafe lifecycle. Just confirming this is intentional and not a naming oversight.
|
🌟 Thank you for your contribution to the Apache Camel project! 🌟 🐫 Apache Camel Committers, please review the following items:
|
|
🧪 CI tested the following changed modules:
🔬 Scalpel shadow comparison — Scalpel: 10 tested, 27 compile-only — current: 10 all testedMaveniverse Scalpel detected 37 affected modules (current approach: 10).
|
| Module | Duration | Status |
|---|---|---|
| Camel :: Launcher | 49.5s | SUCCESS |
| Camel :: AS2 :: Component | 46.3s | SUCCESS |
| Camel :: JBang :: MCP | 37.5s | SUCCESS |
| Camel :: JBang :: Plugin :: TUI | 21.0s | SUCCESS |
| Camel :: Component DSL | 19.6s | SUCCESS |
| Camel :: AS2 :: API | 16.9s | SUCCESS |
| Camel :: JBang :: Plugin :: Kubernetes | 16.3s | SUCCESS |
| Camel :: Catalog :: Camel Catalog | 14.5s | SUCCESS |
| Camel :: Docs | 14.3s | SUCCESS |
| Camel :: Kamelet Main | 8.7s | SUCCESS |
| Camel :: Catalog :: Camel Route Parser | 8.4s | SUCCESS |
| Camel :: JBang :: Plugin :: Testing | 7.8s | SUCCESS |
| Camel :: Catalog :: Camel Report Maven Plugin | 7.6s | SUCCESS |
| Camel :: YAML DSL :: Deserializers | 6.1s | SUCCESS |
| Camel :: All Components Sync point | 5.7s | SUCCESS |
| Camel :: YAML DSL :: Validator | 4.8s | SUCCESS |
| Camel :: YAML DSL :: Maven Plugins | 3.2s | SUCCESS |
| Camel :: Catalog :: Suggest | 3.0s | SUCCESS |
| Camel :: Catalog :: Maven | 3.0s | SUCCESS |
| Camel :: YAML DSL :: Validator Maven Plugin | 2.4s | SUCCESS |
| Camel :: Assembly | 1.7s | SUCCESS |
| Camel :: JBang :: Plugin :: Generate | 1.7s | SUCCESS |
| Camel :: Coverage | 1.6s | SUCCESS |
| Camel :: JBang :: Plugin :: Edit | 1.4s | SUCCESS |
| Camel :: Catalog :: Dummy Component | 1.2s | SUCCESS |
| Camel :: JBang :: Main | 1.2s | SUCCESS |
| Camel :: JBang :: Plugin :: Validate | 0.9s | SUCCESS |
| Camel :: JBang :: Plugin :: Route Parser | 0.8s | SUCCESS |
| Camel :: Endpoint DSL :: Support | 0.8s | SUCCESS |
| Camel :: Catalog :: Console | 0.7s | SUCCESS |
| Camel :: Launcher :: Container | 0.7s | SUCCESS |
| Camel :: JBang :: Integration tests | 0.7s | SUCCESS |
| Camel :: JBang :: Plugin :: MCP | 0.5s | SUCCESS |
| Camel :: Endpoint DSL | n/a | |
| Camel :: Integration Tests | n/a | |
| Camel :: JBang :: Core | n/a | |
| Camel :: YAML DSL | n/a |
Top 20 slowest modules:
Camel :: Launcher(49.5s)Camel :: AS2 :: Component(46.3s)Camel :: JBang :: MCP(37.5s)Camel :: JBang :: Plugin :: TUI(21.0s)Camel :: Component DSL(19.6s)Camel :: AS2 :: API(16.9s)Camel :: JBang :: Plugin :: Kubernetes(16.3s)Camel :: Catalog :: Camel Catalog(14.5s)Camel :: Docs(14.3s)Camel :: Kamelet Main(8.7s)Camel :: Catalog :: Camel Route Parser(8.4s)Camel :: JBang :: Plugin :: Testing(7.8s)Camel :: Catalog :: Camel Report Maven Plugin(7.6s)Camel :: YAML DSL :: Deserializers(6.1s)Camel :: All Components Sync point(5.7s)Camel :: YAML DSL :: Validator(4.8s)Camel :: YAML DSL :: Maven Plugins(3.2s)Camel :: Catalog :: Suggest(3.0s)Camel :: Catalog :: Maven(3.0s)Camel :: YAML DSL :: Validator Maven Plugin(2.4s)
Issue
CAMEL-24435
Problem
AS2ServerConnection.RequestHandlerThread.run()creates itsHttpContextonce, outside therequest loop, and reuses it for every request handled on that connection:
ResponseMDNsetsRECIPIENT_ADDRESSandASYNCHRONOUS_MDNon that context while handling a requestthat carried
Receipt-Delivery-Option, and nothing removed them afterwards. A later request on thesame connection that did not ask for an asynchronous receipt therefore still found a non-null
recipient address, and the handler dispatched a second asynchronous MDN — carrying whatever report was
still stored under
ASYNCHRONOUS_MDN— to the address supplied by the earlier request.Fix
Both attributes are removed in a
finallyafter every request. Usingfinallyrather than clearingat the end of the send block also covers the paths that skip the send — in particular a request URI
with no registered consumer configuration, where
configis null and the old code left the addressbehind untouched.
Also clears
CURRENT_CONSUMER_CONFIGThe same
finallyclears theCURRENT_CONSUMER_CONFIGThreadLocal, which is not in the issue textbut has the identical defect.
setupConfigurationForRequestreturns early without setting it whena path has no registered configuration:
so the thread kept the previous request's wrapper. On its own that cannot trigger a send, but paired
with a new legitimate recipient address it would have signed the MDN with the previous path's key.
Clearing only the context attributes would have left that half of the leak in place.
The post-processing block is extracted into
handleAsynchronousMDNso thefinallyreads clearly;the logic inside is unchanged.
Test
AS2AsyncMdnContextReuseTestsends two messages through oneas2://client/sendroute, so both travelover the same pooled connection to the server:
receiptDeliveryOption→ exactly one MDN must reach the receipt endpoint;The second assertion uses
setAssertPeriod(5s)so a leaked delivery has time to arrive rather thanthe test passing simply by being quick.
Verified both ways: against the unfixed
camel-as2-apithe test fails withmock://receipts Received message count. Expected: <0> but was: <1>, and it passes with the fix.components/camel-as2suite green (API + Component). Full reactor build clean.Documentation
No upgrade-guide entry: the previous behaviour was sending an MDN nobody asked for, to an address from
an unrelated earlier request. There is nothing for a user to migrate.
Claude Code on behalf of oscerd
🤖 Generated with Claude Code