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