CAMEL-24145: camel-saga - InMemorySagaCoordinator compensate/complete now propagate finalization outcome#24829
Conversation
… now propagate finalization outcome Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com> Signed-off-by: Claus Ibsen <claus.ibsen@gmail.com>
|
🌟 Thank you for your contribution to the Apache Camel project! 🌟 🐫 Apache Camel Committers, please review the following items:
|
gnodet
left a comment
There was a problem hiding this comment.
Claude Code on behalf of gnodet
LGTM — fixes a significant behavioral bug where saga finalization was fire-and-forget.
The old code called doCompensate(exchange) / doComplete(exchange) inside the if (doAction) block but then fell through to return CompletableFuture.completedFuture(null) — the actual finalization future was discarded, so the exchange completed immediately and finalization failures were silently swallowed.
Review notes:
-
Core fix — returning the actual future from
doCompensate()/doComplete()with a.thenApply()that convertsfalse(all retries exhausted) into aRuntimeCamelExceptionis the right approach. The exchange now waits for finalization to complete and sees failures. -
Consistent with
camel-lra— aligns the in-memory coordinator with the LRA coordinator's behavior of propagating finalization failures, which is the correct contract for callers that need to know whether compensation/completion succeeded. -
Clean refactoring — flattening the
elseinto a fall-through after the earlyreturnis structurally cleaner while being functionally identical for the non-action path (already COMPENSATING/COMPENSATED or COMPLETING/COMPLETED). -
Tests — both paths (completion and compensation failure propagation) are tested using the existing
SagaFailuresTestinfrastructure withmaxFailures=3to exhaust retries. -
Upgrade guide — clearly documents the behavior change and provides a migration path (
MANUALcompletion mode) for routes that intentionally relied on fire-and-forget finalization. Well-phrased. -
Composability with #24827 — no conflict; this PR modifies
compensate()/complete()while #24827 modifiesdoCompensate()/doComplete(). Both changes compose cleanly in the future chain.
|
🧪 CI tested the following changed modules:
🔬 Scalpel shadow comparison — Scalpel: 551 tested, 28 compile-only — current: 550 all testedMaveniverse Scalpel detected 579 affected modules (current approach: 550).
|
oscerd
left a comment
There was a problem hiding this comment.
LGTM — real fix. compensate()/complete() previously discarded the doFinalize future, so a compensation/completion that exhausted its retries reported success. Returning doCompensate(...).thenApply(res -> { if (!res) throw new RuntimeCamelException(...); return null; }) makes a false outcome surface as an exception that SagaProcessor.handleSagaCompletion sets on the exchange, so failures are no longer swallowed. The new SagaFailuresTest cases exercise real retry-exhaustion (maxRetryAttempts/maxFailures) and assert result.getException(), and the behavior change + MANUAL migration is documented in the 4.22 upgrade guide. Composes cleanly with the terminal-status handling.
Reviewed with Claude Code on behalf of Andrea Cosentino. This review was generated by an AI agent and may contain inaccuracies; please verify all suggestions before applying.
Summary
InMemorySagaCoordinator.compensate()andcomplete()returnedCompletableFuture.completedFuture(null)unconditionally, meaning the exchange finished before any compensation/completion endpoint was invoked and finalization failures never propagated to the caller.compensate()andcomplete()now return the actual future fromdoCompensate()/doComplete()so the exchange waits for finalization (including retries) to finishRuntimeCamelExceptionon the exchange, consistent with thecamel-lracoordinator behaviorFixes: CAMEL-24145
Claude Code on behalf of davsclaus
Co-Authored-By: Claude Opus 4.6 noreply@anthropic.com