This is an automated email from the ASF dual-hosted git repository.
davsclaus pushed a commit to branch main
in repository https://gitbox.apache.org/repos/asf/camel.git
The following commit(s) were added to refs/heads/main by this push:
new e833ea86fe9e CAMEL-24145: camel-saga - InMemorySagaCoordinator
compensate/complete now propagate finalization outcome
e833ea86fe9e is described below
commit e833ea86fe9e53465f4aa945b6fd0617f455c9f4
Author: Claus Ibsen <[email protected]>
AuthorDate: Fri Jul 17 14:08:14 2026 +0200
CAMEL-24145: camel-saga - InMemorySagaCoordinator compensate/complete now
propagate finalization outcome
Co-Authored-By: Claude Opus 4.6 <[email protected]>
Signed-off-by: Claus Ibsen <[email protected]>
Co-authored-by: Claude Opus 4.6 <[email protected]>
---
.../apache/camel/processor/SagaFailuresTest.java | 21 +++++++++++
.../apache/camel/saga/InMemorySagaCoordinator.java | 44 ++++++++++++++--------
.../ROOT/pages/camel-4x-upgrade-guide-4_22.adoc | 17 +++++++++
3 files changed, 66 insertions(+), 16 deletions(-)
diff --git
a/core/camel-core/src/test/java/org/apache/camel/processor/SagaFailuresTest.java
b/core/camel-core/src/test/java/org/apache/camel/processor/SagaFailuresTest.java
index 3ea66d96c136..67d8d20636be 100644
---
a/core/camel-core/src/test/java/org/apache/camel/processor/SagaFailuresTest.java
+++
b/core/camel-core/src/test/java/org/apache/camel/processor/SagaFailuresTest.java
@@ -19,11 +19,14 @@ package org.apache.camel.processor;
import java.util.concurrent.atomic.AtomicInteger;
import org.apache.camel.ContextTestSupport;
+import org.apache.camel.Exchange;
import org.apache.camel.builder.RouteBuilder;
import org.apache.camel.component.mock.MockEndpoint;
import org.apache.camel.saga.InMemorySagaService;
import org.junit.jupiter.api.Test;
+import static org.junit.jupiter.api.Assertions.assertNotNull;
+
public class SagaFailuresTest extends ContextTestSupport {
private AtomicInteger maxFailures;
@@ -86,6 +89,24 @@ public class SagaFailuresTest extends ContextTestSupport {
end.assertIsSatisfied();
}
+ @Test
+ public void testCompletionFailurePropagates() throws Exception {
+ maxFailures = new AtomicInteger(3);
+
+ Exchange result = template.send("direct:saga-complete", e ->
e.getMessage().setBody("hello"));
+
+ assertNotNull(result.getException(), "Completion failure should
propagate to exchange");
+ }
+
+ @Test
+ public void testCompensationFailurePropagates() throws Exception {
+ maxFailures = new AtomicInteger(3);
+
+ Exchange result = template.send("direct:saga-compensate", e ->
e.getMessage().setBody("hello"));
+
+ assertNotNull(result.getException(), "Compensation failure should
propagate to exchange");
+ }
+
@Override
protected RouteBuilder createRouteBuilder() {
diff --git
a/core/camel-support/src/main/java/org/apache/camel/saga/InMemorySagaCoordinator.java
b/core/camel-support/src/main/java/org/apache/camel/saga/InMemorySagaCoordinator.java
index 0a29ad5b6e17..3d85357076d9 100644
---
a/core/camel-support/src/main/java/org/apache/camel/saga/InMemorySagaCoordinator.java
+++
b/core/camel-support/src/main/java/org/apache/camel/saga/InMemorySagaCoordinator.java
@@ -123,14 +123,20 @@ public class InMemorySagaCoordinator implements
CamelSagaCoordinator {
boolean doAction = currentStatus.compareAndSet(Status.RUNNING,
Status.COMPENSATING);
if (doAction) {
- doCompensate(exchange);
- } else {
- Status status = currentStatus.get();
- if (status != Status.COMPENSATING && status != Status.COMPENSATED)
{
- CompletableFuture<Void> res = new CompletableFuture<>();
- res.completeExceptionally(new IllegalStateException("Cannot
compensate: status is " + status));
- return res;
- }
+ return doCompensate(exchange).thenApply(res -> {
+ if (!res) {
+ throw new RuntimeCamelException(
+ "Unable to compensate all required steps of the
saga " + sagaId);
+ }
+ return null;
+ });
+ }
+
+ Status status = currentStatus.get();
+ if (status != Status.COMPENSATING && status != Status.COMPENSATED) {
+ CompletableFuture<Void> res = new CompletableFuture<>();
+ res.completeExceptionally(new IllegalStateException("Cannot
compensate: status is " + status));
+ return res;
}
return CompletableFuture.completedFuture(null);
@@ -141,14 +147,20 @@ public class InMemorySagaCoordinator implements
CamelSagaCoordinator {
boolean doAction = currentStatus.compareAndSet(Status.RUNNING,
Status.COMPLETING);
if (doAction) {
- doComplete(exchange);
- } else {
- Status status = currentStatus.get();
- if (status != Status.COMPLETING && status != Status.COMPLETED) {
- CompletableFuture<Void> res = new CompletableFuture<>();
- res.completeExceptionally(new IllegalStateException("Cannot
complete: status is " + status));
- return res;
- }
+ return doComplete(exchange).thenApply(res -> {
+ if (!res) {
+ throw new RuntimeCamelException(
+ "Unable to complete all required steps of the saga
" + sagaId);
+ }
+ return null;
+ });
+ }
+
+ Status status = currentStatus.get();
+ if (status != Status.COMPLETING && status != Status.COMPLETED) {
+ CompletableFuture<Void> res = new CompletableFuture<>();
+ res.completeExceptionally(new IllegalStateException("Cannot
complete: status is " + status));
+ return res;
}
return CompletableFuture.completedFuture(null);
diff --git
a/docs/user-manual/modules/ROOT/pages/camel-4x-upgrade-guide-4_22.adoc
b/docs/user-manual/modules/ROOT/pages/camel-4x-upgrade-guide-4_22.adoc
index 27935d3a613a..f405ad893c40 100644
--- a/docs/user-manual/modules/ROOT/pages/camel-4x-upgrade-guide-4_22.adoc
+++ b/docs/user-manual/modules/ROOT/pages/camel-4x-upgrade-guide-4_22.adoc
@@ -563,6 +563,23 @@ stylesheet-driven external fetches.
Users of Saxon Professional or Enterprise editions who rely on Java extension
functions
called from XSLT stylesheets must now explicitly set `secureProcessing=false`
on the endpoint.
+=== camel-core - InMemorySagaCoordinator now propagates finalization outcome
+
+The `InMemorySagaCoordinator` used for the Saga EIP (when no external LRA
coordinator is configured)
+previously returned an already-completed future from `compensate()` and
`complete()`, meaning the
+exchange finished before any compensation or completion endpoints had been
invoked. Finalization
+failures were only logged as warnings and never propagated to the caller.
+
+The coordinator now returns the actual finalization future, so the exchange
waits for the
+compensation or completion callbacks to finish (including retries). If all
retry attempts fail,
+the failure is propagated as an exception on the exchange, consistent with the
`camel-lra`
+coordinator behavior.
+
+This is a behavior change: routes that previously completed immediately
regardless of
+compensation/completion outcome will now wait for finalization and may see
exceptions
+that were previously swallowed. If your route relies on fire-and-forget saga
finalization,
+consider using `MANUAL` completion mode instead.
+
=== camel-xslt / camel-xslt-saxon - transformerFactoryConfigurationStrategy
now honored
The `transformerFactoryConfigurationStrategy` option is now applied on all
factory creation paths.