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]
