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 0866b1f8e963 CAMEL-26869: camel-cluster - a ClusteredRouteController 
stopped while the CamelContext runs stops only a cluster service it started 
(#29670)
0866b1f8e963 is described below

commit 0866b1f8e963f577004445a09538ddfdcdceb6c2
Author: allthingssecurity <[email protected]>
AuthorDate: Thu Nov 12 14:30:45 2026 +0530

    CAMEL-26869: camel-cluster - a ClusteredRouteController stopped while the 
CamelContext runs stops only a cluster service it started (#29670)
    
    When a ClusteredRouteController was stopped or shut down while the
    CamelContext kept running (for example the old controller after
    setRouteController replaced it), it stopped its cluster service. By
    default that is the cluster service of the CamelContext, which the
    replacing controller, the cluster route policies and the master consumers
    of the CamelContext share: they all gave up their leadership, and nothing
    started the service again until the CamelContext restarted.
    
    The route controller now remembers whether it started the cluster service
    in doStart, and while the CamelContext runs it stops the service only if
    it started it and the service is not a service of the CamelContext (a
    cluster service set on the controller that nobody else started is still
    stopped, as before). When the CamelContext stops, the cluster service is
    stopped as before, once the routes have stopped (CAMEL-26756).
    
    The 4.24 upgrade guide section of CAMEL-26756 gets a paragraph.
    
    Co-authored-by: Claude Opus 5.5 <[email protected]>
---
 .../impl/cluster/ClusteredRouteController.java     | 21 ++++++-
 .../cluster/ClusteredRouteControllerStopTest.java  | 70 ++++++++++++++++++++++
 .../ROOT/pages/camel-4x-upgrade-guide-4_24.adoc    |  7 +++
 3 files changed, 96 insertions(+), 2 deletions(-)

diff --git 
a/core/camel-cluster/src/main/java/org/apache/camel/impl/cluster/ClusteredRouteController.java
 
b/core/camel-cluster/src/main/java/org/apache/camel/impl/cluster/ClusteredRouteController.java
index cd498ca6a86f..e23d8b1a3a1c 100644
--- 
a/core/camel-cluster/src/main/java/org/apache/camel/impl/cluster/ClusteredRouteController.java
+++ 
b/core/camel-cluster/src/main/java/org/apache/camel/impl/cluster/ClusteredRouteController.java
@@ -64,6 +64,9 @@ public class ClusteredRouteController extends 
DefaultRouteController {
     private final RoutesStoppedListener routesStoppedListener;
     private final LifecycleStrategy contextStoppedStrategy;
     private CamelClusterService clusterService;
+    // whether this route controller started the cluster service when it 
started (and may stop it while the CamelContext
+    // runs)
+    private volatile boolean startedClusterService;
     private CamelClusterService.Selector clusterServiceSelector;
 
     public ClusteredRouteController() {
@@ -152,7 +155,8 @@ public class ClusteredRouteController extends 
DefaultRouteController {
 
     /**
      * Set the cluster service to use. The route controller starts it if it is 
not started yet, and stops it when the
-     * CamelContext stops, once the routes have stopped.
+     * CamelContext stops, once the routes have stopped. When the route 
controller is stopped while the CamelContext
+     * runs, it stops the cluster service only if it started it and it is not 
a service of the CamelContext.
      */
     public void setClusterService(CamelClusterService clusterService) {
         ObjectHelper.notNull(clusterService, "CamelClusterService");
@@ -199,9 +203,11 @@ public class ClusteredRouteController extends 
DefaultRouteController {
         LOGGER.debug("Using ClusterService instance {} (id={}, type={})", 
clusterService, clusterService.getId(),
                 clusterService.getClass().getName());
 
+        startedClusterService = false;
         if (!ServiceHelper.isStarted(clusterService)) {
             // Start the cluster service if not yet started.
             clusterService.start();
+            startedClusterService = true;
         }
 
         if 
(!context.getManagementStrategy().getEventNotifiers().contains(routesStoppedListener))
 {
@@ -223,7 +229,18 @@ public class ClusteredRouteController extends 
DefaultRouteController {
             return;
         }
 
-        stopClusterService();
+        // The route controller is stopped while the CamelContext runs: it 
stops the cluster service only if it started
+        // it, and if it is not a service of the CamelContext. Others may use 
that cluster service (the cluster route
+        // policies and master consumers of the CamelContext, or another route 
controller that replaces this one), and
+        // stopping it would make them give up their leadership
+        CamelContext context = getCamelContext();
+        if (startedClusterService && (context == null || 
!context.hasService(clusterService))) {
+            stopClusterService();
+        } else {
+            LOGGER.debug("The cluster service {} is not stopped: it was not 
started by this route controller, or it is"
+                         + " a service of the CamelContext",
+                    clusterService);
+        }
     }
 
     @Override
diff --git 
a/core/camel-core/src/test/java/org/apache/camel/cluster/ClusteredRouteControllerStopTest.java
 
b/core/camel-core/src/test/java/org/apache/camel/cluster/ClusteredRouteControllerStopTest.java
index d41457612527..9905b745105e 100644
--- 
a/core/camel-core/src/test/java/org/apache/camel/cluster/ClusteredRouteControllerStopTest.java
+++ 
b/core/camel-core/src/test/java/org/apache/camel/cluster/ClusteredRouteControllerStopTest.java
@@ -185,6 +185,76 @@ public class ClusteredRouteControllerStopTest {
         }
     }
 
+    @Test
+    public void 
testCamelContextClusterServiceNotStoppedWhenControllerStopsWhileCamelContextRuns()
 throws Exception {
+        TestClusterService cs = new TestClusterService("cs");
+        CamelContext context = new DefaultCamelContext();
+        // a service of the CamelContext, which the route controller finds and 
shares with the other users of the
+        // cluster service of the CamelContext
+        context.addService(cs);
+        ClusteredRouteController controller = new ClusteredRouteController();
+        controller.setNamespace("my-ns");
+        context.setRouteController(controller);
+        context.start();
+        try {
+            assertTrue(cs.isStarted());
+
+            controller.stop();
+            assertTrue(cs.isStarted(), "the cluster service of the 
CamelContext must keep running");
+        } finally {
+            context.stop();
+        }
+        // the CamelContext stops its service
+        assertFalse(cs.isStarted());
+    }
+
+    @Test
+    public void 
testOldControllerShutdownKeepsTheClusterServiceSharedWithTheNewController() 
throws Exception {
+        TestClusterService cs = new TestClusterService("cs");
+        CamelContext context = new DefaultCamelContext();
+        context.addService(cs);
+        ClusteredRouteController old = new ClusteredRouteController();
+        old.setNamespace("my-ns");
+        context.setRouteController(old);
+        context.start();
+        try {
+            // the route controller is replaced while the CamelContext runs, 
and the old one shut down
+            ClusteredRouteController replacement = new 
ClusteredRouteController();
+            replacement.setNamespace("my-ns");
+            // setRouteController starts the new route controller, as the 
CamelContext is started
+            context.setRouteController(replacement);
+            assertTrue(replacement.isStarted());
+            assertTrue(cs.isStarted());
+
+            old.shutdown();
+            assertTrue(cs.isStarted(), "the new route controller still uses 
the cluster service");
+            assertTrue(replacement.isStarted());
+        } finally {
+            context.stop();
+        }
+    }
+
+    @Test
+    public void 
testClusterServiceStartedByOthersNotStoppedWhenControllerStops() throws 
Exception {
+        TestClusterService cs = new TestClusterService("cs");
+        CamelContext context = new DefaultCamelContext();
+        ClusteredRouteController controller = new ClusteredRouteController();
+        // a cluster service that the application started itself, and set on 
the route controller
+        cs.setCamelContext(context);
+        cs.start();
+        controller.setClusterService(cs);
+        controller.setNamespace("my-ns");
+        context.setRouteController(controller);
+        context.start();
+        try {
+            controller.stop();
+            assertTrue(cs.isStarted(), "the route controller did not start the 
cluster service, so it must not stop it");
+        } finally {
+            context.stop();
+            cs.stop();
+        }
+    }
+
     /**
      * Records whether the cluster service is still started when the 
CamelContext has stopped, before the fallback of
      * the route controller (whose lifecycle strategy is added after this one, 
when the route controller starts).
diff --git 
a/docs/user-manual/modules/ROOT/pages/camel-4x-upgrade-guide-4_24.adoc 
b/docs/user-manual/modules/ROOT/pages/camel-4x-upgrade-guide-4_24.adoc
index e53d680725de..000b0893020f 100644
--- a/docs/user-manual/modules/ROOT/pages/camel-4x-upgrade-guide-4_24.adoc
+++ b/docs/user-manual/modules/ROOT/pages/camel-4x-upgrade-guide-4_24.adoc
@@ -7483,6 +7483,13 @@ over the leadership later than before, by up to the 
shutdown timeout of the Came
 hand over sooner, lower the shutdown timeout (`camel.main.shutdownTimeout`, or
 `CamelContext.getShutdownStrategy().setTimeout`).
 
+When a `ClusteredRouteController` is stopped or shut down while the 
CamelContext keeps running (for example after it
+was replaced by another route controller), it now stops its cluster service 
only if it started that service itself and
+the service is not a service of the CamelContext (Camel Main, Spring Boot and 
Quarkus add the cluster services they
+configure to the CamelContext). Before, it always stopped it, so the cluster 
route policies, master consumers and route
+controllers that share the cluster service of the CamelContext gave up their 
leadership too. To leave the cluster while
+the CamelContext runs, stop the cluster service itself.
+
 === camel-quartz - a route that keeps its trigger fires again when it is added 
again or after a restart
 
 With `deleteJob=false` (as with `pauseJob=true`), a route pauses its trigger 
when it stops, and only the same endpoint

Reply via email to