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]

Reply via email to