shashank created CAMEL-25011:
--------------------------------
Summary: 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
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)