This is an automated email from the ASF dual-hosted git repository. davsclaus pushed a commit to branch fix/CAMEL-24150 in repository https://gitbox.apache.org/repos/asf/camel.git
commit 45ab56b132c019feba0325b4b8a43930520d6046 Author: Claus Ibsen <[email protected]> AuthorDate: Fri Jul 17 10:57:45 2026 +0200 CAMEL-24150: Do not stop shared saga service from individual route processor The SagaProcessor.doStop() was calling ServiceHelper.stopService() on the context-level CamelSagaService. Since this service is shared across all saga routes, stopping one route would shut down the saga service for the entire CamelContext, breaking compensation on all other routes. Co-Authored-By: Claude Opus 4.6 <[email protected]> Signed-off-by: Claus Ibsen <[email protected]> --- .../apache/camel/processor/saga/SagaProcessor.java | 12 --- .../processor/SagaSharedServiceRouteStopTest.java | 86 ++++++++++++++++++++++ 2 files changed, 86 insertions(+), 12 deletions(-) diff --git a/core/camel-core-processor/src/main/java/org/apache/camel/processor/saga/SagaProcessor.java b/core/camel-core-processor/src/main/java/org/apache/camel/processor/saga/SagaProcessor.java index d88c5965dc40..6f4e85e90fb5 100644 --- a/core/camel-core-processor/src/main/java/org/apache/camel/processor/saga/SagaProcessor.java +++ b/core/camel-core-processor/src/main/java/org/apache/camel/processor/saga/SagaProcessor.java @@ -30,7 +30,6 @@ import org.apache.camel.saga.CamelSagaStep; import org.apache.camel.spi.IdAware; import org.apache.camel.spi.RouteIdAware; import org.apache.camel.spi.StepIdAware; -import org.apache.camel.support.service.ServiceHelper; import org.apache.camel.util.ObjectHelper; /** @@ -171,15 +170,4 @@ public abstract class SagaProcessor extends BaseDelegateProcessorSupport } } - @Override - protected void doStart() throws Exception { - super.doStart(); - ServiceHelper.startService(sagaService); - } - - @Override - protected void doStop() throws Exception { - super.doStop(); - ServiceHelper.stopService(sagaService); - } } diff --git a/core/camel-core/src/test/java/org/apache/camel/processor/SagaSharedServiceRouteStopTest.java b/core/camel-core/src/test/java/org/apache/camel/processor/SagaSharedServiceRouteStopTest.java new file mode 100644 index 000000000000..0eb76a7fa2e7 --- /dev/null +++ b/core/camel-core/src/test/java/org/apache/camel/processor/SagaSharedServiceRouteStopTest.java @@ -0,0 +1,86 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.apache.camel.processor; + +import java.util.concurrent.TimeUnit; + +import org.apache.camel.ContextTestSupport; +import org.apache.camel.builder.RouteBuilder; +import org.apache.camel.component.mock.MockEndpoint; +import org.apache.camel.saga.InMemorySagaService; +import org.apache.camel.support.service.ServiceHelper; +import org.junit.jupiter.api.Test; + +import static org.junit.jupiter.api.Assertions.assertTrue; + +public class SagaSharedServiceRouteStopTest extends ContextTestSupport { + + @Test + public void testStoppingOneSagaRouteKeepsSharedServiceRunning() throws Exception { + InMemorySagaService sagaService = context.hasService(InMemorySagaService.class); + + assertTrue(ServiceHelper.isStarted(sagaService), "Saga service should be running"); + + context.getRouteController().stopRoute("route-a"); + + assertTrue(ServiceHelper.isStarted(sagaService), + "Saga service should still be running after stopping one route"); + } + + @Test + public void testCompensationOnOtherRouteStillWorksAfterStoppingOneSagaRoute() throws Exception { + MockEndpoint compensated = getMockEndpoint("mock:compensate-b"); + compensated.expectedMessageCount(1); + + context.getRouteController().stopRoute("route-a"); + + try { + template.sendBody("direct:route-b", "trigger-fail"); + } catch (Exception e) { + // expected — the saga processor throws after compensation is triggered + } + + MockEndpoint.assertIsSatisfied(context, 10, TimeUnit.SECONDS); + } + + @Override + protected RouteBuilder createRouteBuilder() throws Exception { + return new RouteBuilder() { + @Override + public void configure() throws Exception { + InMemorySagaService sagaService = new InMemorySagaService(); + context.addService(sagaService); + + from("direct:route-a").routeId("route-a") + .saga().compensation("direct:compensate-a") + .log("Route A"); + + from("direct:compensate-a") + .log("Compensating A"); + + from("direct:route-b").routeId("route-b") + .saga().compensation("direct:compensate-b") + .process(e -> { + throw new RuntimeException("forced failure"); + }); + + from("direct:compensate-b") + .to("mock:compensate-b"); + } + }; + } +}
