[
https://issues.apache.org/jira/browse/CAMEL-25005?page=com.atlassian.jira.plugin.system.issuetabpanels:comment-tabpanel&focusedCommentId=18118912#comment-18118912
]
shashank commented on CAMEL-25005:
----------------------------------
PR: https://github.com/apache/camel/pull/26865
_Claude Code on behalf of allthingssecurity_
> In-memory Saga: a step that joins while the saga is being completed,
> compensated or timed out is reported as joined, but its
> compensation/completion never runs
> ---------------------------------------------------------------------------------------------------------------------------------------------------------------
>
> Key: CAMEL-25005
> URL: https://issues.apache.org/jira/browse/CAMEL-25005
> Project: Camel
> Issue Type: Bug
> Components: camel-core
> Reporter: shashank
> Priority: Minor
>
> {{InMemorySagaCoordinator.beginStep}} (core/camel-support,
> {{InMemorySagaCoordinator.java}}) is check-then-act without any lock:
> # {{:80-85}} reads {{currentStatus}} and continues if it is {{RUNNING}},
> # {{:87-104}} evaluates the step's saga options (user expressions),
> # {{:105}} adds the step to {{enlistments}} and returns a completed future,
> so the step body runs.
> {{complete()}}, {{compensate()}} and the timeout task switch the status with
> {{compareAndSet(RUNNING, ...)}} ({{:109}}, {{:122}}, {{:147}}) and
> {{doFinalize}} then takes a snapshot of the steps with
> {{reversed(enlistments)}} ({{:196}}). Nothing orders the snapshot after the
> enlistment of a step that already passed the status check. A step that
> enlists after the snapshot:
> * gets a successful {{beginStep}}, so its action runs (for example, the
> payment is taken),
> * is never passed to its compensation (or completion) endpoint,
> * and the saga ends as COMPENSATED (or COMPLETED) and is removed from the
> service.
> The window is the whole option evaluation plus the status read. The timeout
> task makes it reachable with only synchronous routes: the saga times out
> while a joining step ({{MANDATORY}}/{{REQUIRED}}/{{SUPPORTS}}) is between the
> check and the add.
> *Reproduction* (real classes):
> {code:java}
> from("direct:owner").saga().timeout(300,
> MILLISECONDS).compensation("direct:compOwner")
> .to("direct:payment");
> from("direct:payment").saga().propagation(SagaPropagation.MANDATORY)
> .option("orderId", slowOption) // evaluated inside the window
> .compensation("direct:compPayment")
> .process(e -> paymentTaken++);
> {code}
> {{slowOption}} waits until {{direct:compOwner}} has run (it forces the
> interleaving; any slow option expression or a GC pause does the same). Output:
> {noformat}
> [late] owner exchange failed=true exception=Cannot complete: status is
> COMPENSATED
> [late] payment step action executed=1 (its beginStep returned success)
> [late] owner compensation calls=1 payment compensation calls=0
> {noformat}
> Without any forcing ({{stress}}: timeout 1 ms, owner work 0.7-1.3 ms, plain
> {{simple}} option), 1 of 5000 sagas ended compensated with the payment step
> executed and never compensated.
> The same race exists with {{complete()}}/{{compensate()}} from AUTO
> completion when a participant joins from another thread.
> TLA+: {{ExactlyOnceAtEnd}} ("every step whose beginStep succeeded is
> finalized exactly once") is violated by {{sync_timeout_mandatory}}
> (PCheck(p1) -> TimerFire -> FzSnap -> FzRun -> FzFinal -> PEnlist(p1)) and
> {{async_mandatory}}. {{NeverBoth}}, {{AtMostOnce}} and {{KindMatches}} hold.
> *Proposed fix:* make the status check and the enlistment atomic with respect
> to the transition to a final state. For example, a lock in the coordinator:
> * {{beginStep}}: evaluate the options first (outside the lock), then under
> the lock check {{RUNNING}} and add the enlistment (and register the timeout);
> * {{complete()}}, {{compensate()}}, the timeout task: under the same lock do
> the CAS and take the {{enlistments}} snapshot that {{doFinalize}} uses (pass
> it in instead of reading the list later).
> A step that loses the race then fails with {{IllegalStateException("Cannot
> begin: status is ...")}} as it does today when it arrives a moment later. The
> model with this change ({{fix_*}} configs) satisfies all invariants and
> {{Termination}}.
> _Filed with Claude Code on behalf of allthingssecurity._
--
This message was sent by Atlassian Jira
(v8.20.10#820010)