allthingssecurity opened a new pull request, #26869: URL: https://github.com/apache/camel/pull/26869
# Description [CAMEL-25011](https://issues.apache.org/jira/browse/CAMEL-25011) Since CAMEL-23469, the saga id travels in the exchange's internal state (`getExchangeExtension().getSagaLongRunningAction()`). But the copy constructor of `AbstractExchange`, used by `Exchange.copy()`, did not copy it. Only `ExchangeHelper.copyResults` does. The sub-exchanges of split, multicast, recipient list and wire tap are copies, so they kept the saga only through the `Long-Running-Action` header. Since CAMEL-24449, `SagaProcessor` reads that header only when the saga service supports it, and `InMemorySagaService` does not. Together, a saga step reached through split, multicast, recipient list or wire tap no longer saw the saga of its exchange: - `MANDATORY` failed with `Exchange is not part of a saga`; - `REQUIRED` started and completed a saga per sub-exchange, even when the parent saga was compensated; - `SUPPORTS` ran outside of any saga. ```java from("direct:order").saga().compensation("direct:cancelOrder") .split(body()).to("direct:reserveItem").end() .process(e -> { throw new IllegalStateException("payment declined"); }); from("direct:reserveItem").saga() // REQUIRED .compensation("direct:releaseItem").completion("direct:confirmItem") .to("direct:reserve"); ``` Here the order was compensated, but the three reservations were confirmed. The original exchange is affected too: `ExchangeHelper.copyResults` copies the saga id from a copy back to the original exchange, and the copy had none, so it cleared it. After a multicast (default aggregation), recipient list, routing slip, failover load balancer or loop with copy, a following `MANDATORY` step of the same route failed, and a `REQUIRED` step silently started a saga of its own. The MANUAL completion example in the Saga EIP docs (a `seda:operationCompleted` route with `MANDATORY`, then `saga:complete`) is affected as well, because the seda consumer gets a copy. This change: the copy constructor copies the saga id, so a copy belongs to the same saga as the exchange it was copied from. This is how the header behaved before 4.22.1. The internal state is only set by Camel, so a message still cannot pick its saga through the header (CAMEL-24449 is kept). Regression in 4.22.1 and 4.18.5, from the combination of CAMEL-23469 and CAMEL-24449. Tests: new `SagaExchangeCopyTest` under `InMemorySagaService`: split into `MANDATORY` steps, multicast into `REQUIRED` steps, and a `MANDATORY` step of the owner route after a multicast. The parent saga then fails, and every item must be compensated and none completed. Without the fix all three fail: ``` testSplitMandatoryStepsJoinSaga AssertionError: mock://compensate-item Received message count. Expected: <3> but was: <0> testMulticastRequiredStepsJoinSaga AssertionError: mock://compensate-item Received message count. Expected: <2> but was: <0> testStepAfterMulticastJoinsSaga AssertionError: mock://compensate-item Received message count. Expected: <1> but was: <0> ``` With the fix they pass. `*Saga*,*Exchange*Test,*Multicast*,*Split*,*WireTap*,*RecipientList*` in camel-core: 832 tests, 0 failures. Related: #26866 (CAMEL-25006) makes a `REQUIRED`/`SUPPORTS` step fail when its exchange carries the id of a saga that has ended. With this change, copies carry the saga id again, so a copy that reaches such a step after its saga ended (for example through an asynchronous wire tap or seda) fails as well, instead of starting a new saga. That is the behaviour before 4.22 too, when the ended coordinator failed `beginStep`. The two PRs merge without conflicts. Found while modelling the in-memory saga in TLA+ (a participant that cannot see the saga behaves like one arriving after the saga ended, and breaks "no mixed outcome"), then reproduced against the real classes. The reproduction now gives `itemAction=3 compOwner=1 compItem=3 complItem=0` for both MANDATORY and REQUIRED. That is the same result as a service that still reads the header. # 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 modules, 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]
