[
https://issues.apache.org/jira/browse/CAMEL-25011?page=com.atlassian.jira.plugin.system.issuetabpanels:all-tabpanel
]
Claus Ibsen updated CAMEL-25011:
--------------------------------
Fix Version/s: 4.23.0
> 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
> Fix For: 4.23.0
>
>
> 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)