allthingssecurity opened a new pull request, #26865:
URL: https://github.com/apache/camel/pull/26865

   # Description
   
   [CAMEL-25005](https://issues.apache.org/jira/browse/CAMEL-25005)
   
   `InMemorySagaCoordinator.beginStep` was check-then-act without a lock:
   1. it read the status and continued if it was `RUNNING`;
   2. it evaluated the step's saga options (user expressions);
   3. it added the step to the enlistments and returned a completed future, so 
the step's action ran.
   
   `complete()`, `compensate()` and the timeout task switch the status with 
`compareAndSet(RUNNING, ...)`, and `doFinalize` then takes a snapshot of the 
enlistments. Nothing ordered that snapshot after the enlistment of a step that 
had already passed the check. Such a step:
   - got a successful `beginStep`, so its action ran (for example, the payment 
was taken);
   - was never passed to its compensation (or completion) endpoint;
   - while the saga ended as COMPENSATED (or COMPLETED) and was removed.
   
   The window covers the option evaluation. The saga timeout makes it reachable 
with synchronous routes only: the saga times out while a joining step is 
between the check and the enlistment. Without any forcing (timeout 1 ms, owner 
work about 1 ms, plain `simple` option), 1 of 5000 sagas ended compensated with 
the payment step executed and never compensated.
   
   This change:
   - `beginStep` still evaluates the options outside of any lock. Then, under a 
lock of the coordinator, it checks the status again and enlists the step (and 
schedules its timeout).
   - `complete()`, `compensate()` and the timeout task change the status and 
take the snapshot they finalize under the same lock. The snapshot is passed to 
the finalization instead of reading the list later.
   
   A step that loses the race now fails with `IllegalStateException("Cannot 
begin: status is ...")`, as it does when it arrives a moment later. The public 
`doCompensate`/`doComplete`/`doFinalize` methods keep their signatures and 
behaviour. Timeouts can also no longer be scheduled after `cancelTimeouts()` 
ran.
   
   Tests: new `SagaJoinDuringTimeoutTest`. A `MANDATORY` step joins a saga with 
a 100 ms timeout. Its option expression waits (Awaitility) until the saga's 
compensation is running, which puts the enlistment after the snapshot 
deterministically. Without the fix:
   ```
   AssertionError: mock://payment Received message count. Expected: <0> but 
was: <1>
   ```
   (the payment ran, and `mock:compensate-payment` never received anything). 
With the fix, the step fails to begin and the test passes. All `*Saga*` tests 
in camel-core pass (36 tests). The reproduction: `payment step action 
executed=0`, and the stress run found no lost compensations in 5000 sagas.
   
   Found with a TLA+ model of the in-memory saga (coordinator, timeout, 
synchronous and asynchronous participants), then reproduced against the real 
classes. With this change, "every step whose `beginStep` succeeded is completed 
or compensated exactly once" holds, along with "never both" and termination.
   
   This is independent of CAMEL-25006, the other in-memory saga fix I am 
sending, where a REQUIRED/SUPPORTS step of a saga that has already ended starts 
a new saga (a regression from CAMEL-24144). Each fixes its own property in the 
model, and both together satisfy all of them.
   
   # Target
   
   - [x] I checked that the commit is targeting the correct branch (Camel 4 
uses the `main` branch)
   
   # Tracking
   - [x] If this is a large change, bug fix, or code improvement, I checked 
there is a [JIRA issue](https://issues.apache.org/jira/browse/CAMEL) filed for 
the change (usually before you start working on it).
   
   # Apache Camel coding standards and style
   
   - [x] I checked that each commit in the pull request has a meaningful 
subject line and body.
   - [ ] I have run `mvn clean install -DskipTests` locally from root folder 
and I have committed all auto-generated changes.
     (I built and tested the affected modules, including the formatter and 
import-sort plugins. I did not run the full root build.)
   
   # AI-assisted contributions
   
   - [x] If this PR includes AI-generated code, commits have proper 
co-authorship attribution (e.g., `Co-authored-by` trailers) and the PR 
description identifies the AI tool used.
     This PR was prepared with Claude Code (Claude Opus 5.5). The commit 
carries a `Co-Authored-By` trailer.
   
   _Claude Code on behalf of allthingssecurity_
   
   🤖 Generated with [Claude Code](https://claude.com/claude-code)
   


-- 
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.

To unsubscribe, e-mail: [email protected]

For queries about this service, please contact Infrastructure at:
[email protected]

Reply via email to