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 d9a95c67223a CAMEL-25849: camel-disruptor - a disruptor-vm consumer 
keeps consuming when the CamelContext that first used the queue stops (#28322)
d9a95c67223a is described below

commit d9a95c67223a2fdffb207dc3809bc5864d296ce0
Author: allthingssecurity <[email protected]>
AuthorDate: Fri Oct 23 13:52:18 2026 +0530

    CAMEL-25849: camel-disruptor - a disruptor-vm consumer keeps consuming when 
the CamelContext that first used the queue stops (#28322)
    
    The consumer threads of a disruptor-vm queue belonged to the CamelContext 
that first created the queue, so stopping that CamelContext stopped consumption 
for all other CamelContexts. The threads now belong to a CamelContext that uses 
the queue, and move to another one when the last endpoint of their CamelContext 
is removed.
    
    Co-Authored-By: Claude Opus 5.5 <[email protected]>
---
 .../component/disruptor/DisruptorReference.java    | 56 +++++++++++++++++--
 .../vm/DisruptorVmFirstContextStopTest.java        | 65 ++++++++++++++++++++++
 2 files changed, 115 insertions(+), 6 deletions(-)

diff --git 
a/components/camel-disruptor/src/main/java/org/apache/camel/component/disruptor/DisruptorReference.java
 
b/components/camel-disruptor/src/main/java/org/apache/camel/component/disruptor/DisruptorReference.java
index 4317c87a047f..67427a5fc416 100644
--- 
a/components/camel-disruptor/src/main/java/org/apache/camel/component/disruptor/DisruptorReference.java
+++ 
b/components/camel-disruptor/src/main/java/org/apache/camel/component/disruptor/DisruptorReference.java
@@ -39,7 +39,9 @@ import java.util.concurrent.locks.ReentrantLock;
 import com.lmax.disruptor.InsufficientCapacityException;
 import com.lmax.disruptor.RingBuffer;
 import com.lmax.disruptor.dsl.Disruptor;
+import org.apache.camel.CamelContext;
 import org.apache.camel.Exchange;
+import org.apache.camel.RuntimeCamelException;
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
 
@@ -79,6 +81,8 @@ public class DisruptorReference {
 
     //access guarded by this
     private ExecutorService executor;
+    // the CamelContext whose ExecutorServiceManager created the executor, 
which shuts it down when it stops
+    private CamelContext executorContext;
 
     private LifecycleAwareExchangeEventHandler[] handlers = new 
LifecycleAwareExchangeEventHandler[0];
 
@@ -256,17 +260,42 @@ public class DisruptorReference {
         }
     }
 
+    /**
+     * The CamelContext to create the threads of the event handlers with: the 
CamelContext of an endpoint that uses this
+     * Disruptor (with disruptor-vm, the endpoints can belong to several 
CamelContexts), else that of the component that
+     * created it.
+     */
+    private CamelContext resolveExecutorContext() {
+        for (DisruptorEndpoint endpoint : endpoints) {
+            CamelContext context = endpoint.getCamelContext();
+            if (context != null) {
+                return context;
+            }
+        }
+        return component.getCamelContext();
+    }
+
+    private boolean isExecutorContextInUse() {
+        for (DisruptorEndpoint endpoint : endpoints) {
+            if (endpoint.getCamelContext() == executorContext) {
+                return true;
+            }
+        }
+        return false;
+    }
+
     private void resizeThreadPoolExecutor(final int newSize) {
         if (executor == null && newSize > 0) {
             LOGGER.debug("Creating new executor with {} threads", newSize);
             //no thread pool executor yet, create a new one
-            executor = 
component.getCamelContext().getExecutorServiceManager().newFixedThreadPool(this,
 uri,
-                    newSize);
+            executorContext = resolveExecutorContext();
+            executor = 
executorContext.getExecutorServiceManager().newFixedThreadPool(this, uri, 
newSize);
         } else if (executor != null && newSize <= 0) {
             LOGGER.debug("Shutting down executor");
             //we need to shut down our executor
-            
component.getCamelContext().getExecutorServiceManager().shutdown(executor);
+            executorContext.getExecutorServiceManager().shutdown(executor);
             executor = null;
+            executorContext = null;
         } else if (executor instanceof ThreadPoolExecutor threadPoolExecutor) {
             LOGGER.debug("Resizing existing executor to {} threads", newSize);
             //our thread pool executor is of type ThreadPoolExecutor, we know 
how to resize it
@@ -283,10 +312,10 @@ public class DisruptorReference {
         } else if (newSize > 0) {
             LOGGER.debug("Shutting down old and creating new executor with {} 
threads", newSize);
             //hmmm...no idea what kind of executor this is...just kill it and 
start fresh
-            
component.getCamelContext().getExecutorServiceManager().shutdown(executor);
+            executorContext.getExecutorServiceManager().shutdown(executor);
 
-            executor = 
component.getCamelContext().getExecutorServiceManager().newFixedThreadPool(this,
 uri,
-                    newSize);
+            executorContext = resolveExecutorContext();
+            executor = 
executorContext.getExecutorServiceManager().newFixedThreadPool(this, uri, 
newSize);
         }
     }
 
@@ -404,6 +433,21 @@ public class DisruptorReference {
             }
             endpoints.remove(disruptorEndpoint);
             LOGGER.debug("Endpoint removed: {}, new total endpoints {}", 
disruptorEndpoint, getEndpointCount());
+            if (executor != null && !endpoints.isEmpty() && 
!isExecutorContextInUse()) {
+                // a disruptor-vm queue shared with other CamelContexts: the 
CamelContext of the threads of the event
+                // handlers no longer uses the queue, and shuts the threads 
down when it stops, so move them to the
+                // CamelContext of an endpoint that still uses it, or its 
consumers stop consuming for good
+                LOGGER.info("Moving the event handlers of disruptor {} from 
CamelContext {}, which no longer uses it, to"
+                            + " CamelContext {}",
+                        uri, executorContext.getName(), 
resolveExecutorContext().getName());
+                shutdownDisruptor(true);
+                shutdownExecutor();
+                try {
+                    start();
+                } catch (Exception e) {
+                    throw RuntimeCamelException.wrapRuntimeCamelException(e);
+                }
+            }
         } finally {
             lock.unlock();
         }
diff --git 
a/components/camel-disruptor/src/test/java/org/apache/camel/component/disruptor/vm/DisruptorVmFirstContextStopTest.java
 
b/components/camel-disruptor/src/test/java/org/apache/camel/component/disruptor/vm/DisruptorVmFirstContextStopTest.java
new file mode 100644
index 000000000000..28a460159a36
--- /dev/null
+++ 
b/components/camel-disruptor/src/test/java/org/apache/camel/component/disruptor/vm/DisruptorVmFirstContextStopTest.java
@@ -0,0 +1,65 @@
+/*
+ * 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.component.disruptor.vm;
+
+import org.apache.camel.builder.RouteBuilder;
+import org.apache.camel.component.mock.MockEndpoint;
+import org.junit.jupiter.api.Test;
+
+/**
+ * The consumer of a disruptor-vm queue in one CamelContext keeps consuming 
when the CamelContext that first used the
+ * queue is stopped.
+ */
+class DisruptorVmFirstContextStopTest extends AbstractVmTestSupport {
+
+    @Test
+    void consumerKeepsConsumingAfterTheFirstContextStops() throws Exception {
+        MockEndpoint mock = context2.getEndpoint("mock:result", 
MockEndpoint.class);
+        mock.expectedBodiesReceived("first");
+        template.sendBody("direct:test", "first");
+        mock.assertIsSatisfied();
+        mock.reset();
+
+        // the CamelContext that created the queue goes away, the one with the 
consumer goes on
+        context.stop();
+
+        mock.expectedBodiesReceived("second");
+        template2.sendBody("direct:other", "second");
+        mock.assertIsSatisfied();
+    }
+
+    @Override
+    protected RouteBuilder createRouteBuilder() {
+        return new RouteBuilder() {
+            @Override
+            public void configure() {
+                from("direct:test").to("disruptor-vm:foo");
+            }
+        };
+    }
+
+    @Override
+    protected RouteBuilder createRouteBuilderForSecondContext() {
+        return new RouteBuilder() {
+            @Override
+            public void configure() {
+                from("disruptor-vm:foo").to("mock:result");
+                from("direct:other").to("disruptor-vm:foo");
+            }
+        };
+    }
+}

Reply via email to