You are viewing a plain text version of this content. The canonical link for it is here.
Posted to issues@camel.apache.org by "shashank (Jira)" <ji...@apache.org> on 2026/09/24 17:19:00 UTC

[jira] [Created] (CAMEL-25006) 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)

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)