[ 
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)

Reply via email to