shashank created CAMEL-25006:
--------------------------------
Summary: In-memory Saga: after a saga timed out, a REQUIRED step
on the same exchange silently starts and completes a new saga (SUPPORTS runs
outside any saga)
Key: CAMEL-25006
URL: https://issues.apache.org/jira/browse/CAMEL-25006
Project: Camel
Issue Type: Bug
Components: camel-core
Reporter: shashank
Since CAMEL-24144 (4.22.0) {{InMemorySagaCoordinator}} removes itself from
{{InMemorySagaService}} when it reaches COMPENSATED/COMPLETED
({{doCompensate}}/{{doComplete}}, {{InMemorySagaCoordinator.java:170-190}}).
{{getSaga(id)}} then returns {{null}} ({{InMemorySagaService.java:63-65}}), and
the saga processors treat {{null}} as "this exchange is not in a saga":
* {{RequiredSagaProcessor}} creates a new saga
({{RequiredSagaProcessor.java:44-50}}) and, as the new saga is not inherited,
completes it at the end of the step ({{:58-60}});
* {{SupportsSagaProcessor}} runs the step outside any saga
({{SupportsSagaProcessor.java:47-48}});
* {{MandatorySagaProcessor}} fails with "Exchange is not part of a saga"
({{:39-41}}).
The exchange still carries the id of the saga it belongs to. When that saga
timed out, the next REQUIRED step is completed in a saga of its own, while the
saga the exchange belongs to has been compensated. Before CAMEL-24144 the same
step failed at {{beginStep}} with {{IllegalStateException}} (the CAMEL-24144
ticket describes that behaviour), and it still fails that way when it arrives
while the saga is COMPENSATING. So the outcome depends on whether the
compensation already finished.
*Reproduction*:
{code:java}
from("direct:owner").saga().timeout(200,
MILLISECONDS).compensation("direct:compOwner")
.process(e -> Thread.sleep(400)) // a slow call outlasts the saga
timeout
.to("direct:payment");
from("direct:payment").saga() // REQUIRED (default)
.compensation("direct:compPayment").completion("direct:complPayment")
.process(e -> paymentTaken++);
{code}
{noformat}
[stale: payment arrives after the saga was COMPENSATED+removed] owner
failed=true exception=IllegalStateException: Cannot complete: status is
COMPENSATED
[stale: ...] compOwner=1 paymentAction=1 complPayment=1 compPayment=0
[stale control: payment arrives while COMPENSATING] owner failed=true
exception=IllegalStateException: Cannot begin: status is COMPENSATING
[stale control: ...] compOwner=1 paymentAction=0 complPayment=0 compPayment=0
{noformat}
The order was compensated, yet the payment was taken and confirmed (its
completion ran), and nothing will ever compensate it.
TLA+: {{NoMixedOutcome}} (a participant carrying S's id never runs in another
saga or outside a saga while S compensates) is violated by
{{sync_timeout_required}} and {{sync_timeout_supports}} (TimerFire -> FzSnap ->
FzRun -> FzFinal -> PArrive(p1)). With a tombstone for ended sagas ({{fix_*}})
the property holds.
*Proposed fix:* distinguish "no saga id" from "unknown/ended saga id":
* {{InMemorySagaService}} keeps a bounded tombstone of ended saga ids (or the
coordinator object in a small expiring map) and {{getSaga}} returns the ended
coordinator, whose {{beginStep}} fails with "Cannot begin: status is
COMPENSATED/COMPLETED" as before CAMEL-24144; or
* {{SagaProcessor.getCurrentSagaCoordinator}} fails the exchange ("saga <id> is
no longer active") when the exchange carries an id and the service returns
{{null}}, instead of falling through to "no saga".
The PR takes the second option: the in-memory service keeps removing ended
sagas (the memory fix of CAMEL-24144), and REQUIRED/SUPPORTS steps fail when
the exchange carries the id of a saga that the service does not know.
_Filed with Claude Code on behalf of allthingssecurity._
--
This message was sent by Atlassian Jira
(v8.20.10#820010)