[ 
https://issues.apache.org/jira/browse/CAMEL-25011?page=com.atlassian.jira.plugin.system.issuetabpanels:comment-tabpanel&focusedCommentId=18119014#comment-18119014
 ] 

shashank commented on CAMEL-25011:
----------------------------------

PR: https://github.com/apache/camel/pull/26869

_Claude Code on behalf of allthingssecurity_

> Saga EIP with the in-memory saga service: split, multicast, recipient list 
> and wire tap sub-exchanges lose the saga (MANDATORY fails, REQUIRED completes 
> independent sagas)
> ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------
>
>                 Key: CAMEL-25011
>                 URL: https://issues.apache.org/jira/browse/CAMEL-25011
>             Project: Camel
>          Issue Type: Bug
>          Components: camel-core
>            Reporter: shashank
>            Priority: Minor
>
> Since CAMEL-23469 the saga id travels in the exchange extension 
> ({{getSagaLongRunningAction}}). {{AbstractExchange(AbstractExchange 
> parent)}}, used by {{Exchange.copy()}}, does not copy that field 
> ({{AbstractExchange.java:123-159}}). Only {{ExchangeHelper.copyResults}} 
> copies it ({{ExchangeHelper.java:396}}). Sub-exchanges of split, multicast, 
> recipient list and wire tap are created with 
> {{copy()}}/{{createCorrelatedCopy}}, so they have no internal saga id.
> This used to be covered by the {{Long-Running-Action}} header, which is 
> copied with the message. Since CAMEL-24449 (4.22.1, 4.18.5, 4.23.0) 
> {{SagaProcessor.getCurrentSagaCoordinator}} only reads the header when 
> {{sagaService.isLongRunningActionHeaderSupported()}} 
> ({{SagaProcessor.java:57-64}}), which is false for {{InMemorySagaService}}. 
> The combination means a saga step reached through split/multicast/recipient 
> list/wire tap no longer sees the saga:
> * {{MANDATORY}}: fails with "Exchange is not part of a saga";
> * {{REQUIRED}}: each sub-exchange starts its own saga and completes it, even 
> when the parent saga compensates;
> * {{SUPPORTS}}: runs outside any saga.
> *Reproduction*:
> {code:java}
> from("direct:owner").saga().compensation("direct:compOwner")
>     .split(body()).to("direct:item").end()
>     .process(e -> { throw new IllegalStateException("payment declined"); });
> from("direct:item").saga().propagation(MANDATORY /* or REQUIRED */)
>     .compensation("direct:compItem").completion("direct:complItem")
>     .process(e -> reserved++);
> {code}
> Body {{List.of("a","b","c")}}:
> {noformat}
> [copy MANDATORY] owner exception chain=[CamelExchangeException: Exchange is 
> not part of a saga. Exchange[]]
> [copy MANDATORY] itemAction=0 compOwner=1 compItem=0 complItem=0
> [copy REQUIRED]  owner exception chain=[IllegalStateException: payment 
> declined]
> [copy REQUIRED]  itemAction=3 compOwner=1 compItem=0 complItem=3
> [copy MANDATORY+headerSupport] owner exception chain=[IllegalStateException: 
> payment declined]
> [copy MANDATORY+headerSupport] itemAction=3 compOwner=1 compItem=3 complItem=0
> {noformat}
> The last run uses an {{InMemorySagaService}} subclass that returns {{true}} 
> from {{isLongRunningActionHeaderSupported()}}, i.e. the behaviour before 
> CAMEL-24449: the items join the saga and are compensated. With REQUIRED the 
> three reservations are confirmed ({{complItem=3}}) although the order was 
> compensated.
> The TLA+ saga model treats "participant cannot see S" like the stale-id case: 
> {{NoMixedOutcome}} is violated for REQUIRED and SUPPORTS.
> *Proposed fix:* copy the internal saga id with the exchange: set 
> {{this.sagaLongRunningAction = parent.sagaLongRunningAction}} in the 
> {{AbstractExchange}} copy constructor (and in {{ExtendedExchangeExtension}}'s 
> copy path if it has one), so every copy keeps the saga it was created in, 
> independent of the header. That keeps CAMEL-24449's intent (a message cannot 
> pick a saga through the header) because the internal field is only ever set 
> by Camel. Add a test with split + MANDATORY and multicast + REQUIRED under 
> {{InMemorySagaService}}.
> The impact is wider than sub-exchanges running saga steps: 
> {{ExchangeHelper.copyResults}} copies the saga id from a sub-exchange back to 
> the original exchange, and the copy has none, so it clears it. After a 
> multicast (default aggregation), recipient list, routing slip, failover load 
> balancer or loop with copy, the *original* exchange has lost its saga: a 
> following MANDATORY step fails, and a REQUIRED step silently starts a saga of 
> its own. The MANUAL completion example in the saga EIP docs 
> ({{seda:operationCompleted}} with MANDATORY, then {{saga:complete}}) is 
> affected too, as the seda consumer gets a copy.
> _Filed with Claude Code on behalf of allthingssecurity._



--
This message was sent by Atlassian Jira
(v8.20.10#820010)

Reply via email to