This is an automated email from the ASF dual-hosted git repository.

davsclaus pushed a commit to branch camel-4.18.x
in repository https://gitbox.apache.org/repos/asf/camel.git


The following commit(s) were added to refs/heads/camel-4.18.x by this push:
     new 7e1a5ff287e7 [backport camel-4.18.x] CAMEL-24591: camel-seda - 
Re-register released queue reference on route restart
7e1a5ff287e7 is described below

commit 7e1a5ff287e7e9d9ec828d88ba66f2b6868bfff7
Author: Claus Ibsen <[email protected]>
AuthorDate: Wed Sep 2 21:18:56 2026 +0200

    [backport camel-4.18.x] CAMEL-24591: camel-seda - Re-register released 
queue reference on route restart
    
    Co-authored-by: Claude Opus 4.8 <[email protected]>
---
 .../camel/component/seda/QueueReference.java       |  9 ++++
 .../apache/camel/component/seda/SedaEndpoint.java  | 27 +++++++----
 .../seda/SedaConsumerRouteRestartTest.java         | 55 ++++++++++++++++++++++
 .../seda/SedaProducerRouteRestartTest.java         | 51 ++++++++++++++++++++
 .../seda/SedaSharedQueueEndpointRestartTest.java   | 55 ++++++++++++++++++++++
 5 files changed, 187 insertions(+), 10 deletions(-)

diff --git 
a/components/camel-seda/src/main/java/org/apache/camel/component/seda/QueueReference.java
 
b/components/camel-seda/src/main/java/org/apache/camel/component/seda/QueueReference.java
index 28e97fff894a..7b6c42abde85 100644
--- 
a/components/camel-seda/src/main/java/org/apache/camel/component/seda/QueueReference.java
+++ 
b/components/camel-seda/src/main/java/org/apache/camel/component/seda/QueueReference.java
@@ -69,6 +69,15 @@ public final class QueueReference {
         }
     }
 
+    boolean isReferenced(SedaEndpoint endpoint) {
+        lock.lock();
+        try {
+            return endpoints.contains(endpoint);
+        } finally {
+            lock.unlock();
+        }
+    }
+
     /**
      * Gets the reference counter
      */
diff --git 
a/components/camel-seda/src/main/java/org/apache/camel/component/seda/SedaEndpoint.java
 
b/components/camel-seda/src/main/java/org/apache/camel/component/seda/SedaEndpoint.java
index 65958340ebb8..3f91a005e054 100644
--- 
a/components/camel-seda/src/main/java/org/apache/camel/component/seda/SedaEndpoint.java
+++ 
b/components/camel-seda/src/main/java/org/apache/camel/component/seda/SedaEndpoint.java
@@ -216,14 +216,15 @@ public class SedaEndpoint extends DefaultEndpoint 
implements AsyncEndpoint, Brow
     public BlockingQueue<Exchange> getQueue() {
         lock.lock();
         try {
-            if (queue == null) {
+            if (queue == null || (getComponent() != null && (ref == null || 
!ref.isReferenced(this)))) {
                 // prefer to lookup queue from component, so if this endpoint 
is re-created or re-started
                 // then the existing queue from the component can be used, so 
new producers and consumers
-                // can use the already existing queue referenced from the 
component
+                // can use the already existing queue referenced from the 
component; a released or
+                // de-listed reference must not be reused as the component no 
longer tracks it
                 if (getComponent() != null) {
                     // use null to indicate default size (= use what the 
existing queue has been configured with)
                     Integer size = (getSize() == Integer.MAX_VALUE || 
getSize() == SedaConstants.QUEUE_SIZE) ? null : getSize();
-                    QueueReference ref = getComponent().getOrCreateQueue(this, 
size, isMultipleConsumers(), queueFactory);
+                    ref = getComponent().getOrCreateQueue(this, size, 
isMultipleConsumers(), queueFactory);
                     queue = ref.getQueue();
                     String key = getComponent().getQueueKey(getEndpointUri());
                     LOG.debug("Endpoint {} is using shared queue: {} with 
size: {}", this, key,
@@ -578,12 +579,7 @@ public class SedaEndpoint extends DefaultEndpoint 
implements AsyncEndpoint, Brow
 
     void onStarted(SedaProducer producer) {
         producers.add(producer);
-        if (getComponent() != null && (ref == null || queue == null)) {
-            // re-register queue reference when producer restarts after queue 
was released on stop
-            Integer size = (getSize() == Integer.MAX_VALUE || getSize() == 
SedaConstants.QUEUE_SIZE) ? null : getSize();
-            ref = getComponent().getOrCreateQueue(this, size, 
isMultipleConsumers(), queueFactory);
-            queue = ref.getQueue();
-        }
+        registerQueueIfStale();
     }
 
     void onStopped(SedaProducer producer) {
@@ -596,6 +592,7 @@ public class SedaEndpoint extends DefaultEndpoint 
implements AsyncEndpoint, Brow
 
     void onStarted(SedaConsumer consumer) throws Exception {
         consumers.add(consumer);
+        registerQueueIfStale();
         if (isMultipleConsumers()) {
             updateMulticastProcessor();
         }
@@ -608,6 +605,17 @@ public class SedaEndpoint extends DefaultEndpoint 
implements AsyncEndpoint, Brow
         }
     }
 
+    private void registerQueueIfStale() {
+        if (getComponent() != null && (ref == null || queue == null || 
!ref.isReferenced(this))) {
+            // re-register when a producer or consumer restarts after the 
queue was released on stop, or
+            // when this endpoint was dropped from a reference still shared 
with other endpoints; the
+            // stale ref/queue fields may be non-null while no longer 
registered with the component
+            Integer size = (getSize() == Integer.MAX_VALUE || getSize() == 
SedaConstants.QUEUE_SIZE) ? null : getSize();
+            ref = getComponent().getOrCreateQueue(this, size, 
isMultipleConsumers(), queueFactory);
+            queue = ref.getQueue();
+        }
+    }
+
     public boolean hasConsumers() {
         return !this.consumers.isEmpty();
     }
@@ -676,5 +684,4 @@ public class SedaEndpoint extends DefaultEndpoint 
implements AsyncEndpoint, Brow
         queue = null;
         ref = null;
     }
-
 }
diff --git 
a/core/camel-core/src/test/java/org/apache/camel/component/seda/SedaConsumerRouteRestartTest.java
 
b/core/camel-core/src/test/java/org/apache/camel/component/seda/SedaConsumerRouteRestartTest.java
new file mode 100644
index 000000000000..8410b184e255
--- /dev/null
+++ 
b/core/camel-core/src/test/java/org/apache/camel/component/seda/SedaConsumerRouteRestartTest.java
@@ -0,0 +1,55 @@
+/*
+ * 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.seda;
+
+import org.apache.camel.ContextTestSupport;
+import org.apache.camel.builder.RouteBuilder;
+import org.apache.camel.component.mock.MockEndpoint;
+import org.junit.jupiter.api.Test;
+
+class SedaConsumerRouteRestartTest extends ContextTestSupport {
+
+    @Test
+    void testConsumerFirstRestartAfterQueueRelease() throws Exception {
+        // stopping the consumer route first and the producer route last 
releases the shared queue
+        // reference; restarting the consumer route first must not leave its 
pollers on the orphaned
+        // queue while a later restarted producer registers a fresh one
+        context.getRouteController().stopRoute("consumer");
+        context.getRouteController().stopRoute("producer");
+
+        context.getRouteController().startRoute("consumer");
+        context.getRouteController().startRoute("producer");
+
+        MockEndpoint mock = getMockEndpoint("mock:result");
+        mock.expectedBodiesReceived("after-restart");
+
+        template.sendBody("direct:start", "after-restart");
+
+        mock.assertIsSatisfied();
+    }
+
+    @Override
+    protected RouteBuilder createRouteBuilder() {
+        return new RouteBuilder() {
+            @Override
+            public void configure() {
+                from("direct:start").routeId("producer").to("seda:bar");
+                from("seda:bar").routeId("consumer").to("mock:result");
+            }
+        };
+    }
+}
diff --git 
a/core/camel-core/src/test/java/org/apache/camel/component/seda/SedaProducerRouteRestartTest.java
 
b/core/camel-core/src/test/java/org/apache/camel/component/seda/SedaProducerRouteRestartTest.java
new file mode 100644
index 000000000000..cd9a2bf9ffff
--- /dev/null
+++ 
b/core/camel-core/src/test/java/org/apache/camel/component/seda/SedaProducerRouteRestartTest.java
@@ -0,0 +1,51 @@
+/*
+ * 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.seda;
+
+import org.apache.camel.ContextTestSupport;
+import org.apache.camel.builder.RouteBuilder;
+import org.junit.jupiter.api.Test;
+
+import static org.junit.jupiter.api.Assertions.assertDoesNotThrow;
+import static org.junit.jupiter.api.Assertions.assertEquals;
+
+class SedaProducerRouteRestartTest extends ContextTestSupport {
+
+    @Test
+    void testSendAfterProducerRouteRestart() throws Exception {
+        // no call may touch the endpoint's queue reference between stop and 
restart: a stale
+        // non-null reference left over from the stop is exactly what this 
test exercises
+        context.getRouteController().stopRoute("producer");
+        context.getRouteController().startRoute("producer");
+
+        assertDoesNotThrow(() -> template.sendBody("direct:start", 
"after-restart"),
+                "send after producer route restart should be delivered to the 
queue, not fail");
+
+        SedaEndpoint bar = getMandatoryEndpoint("seda:bar", 
SedaEndpoint.class);
+        assertEquals(1, bar.getCurrentQueueSize());
+    }
+
+    @Override
+    protected RouteBuilder createRouteBuilder() {
+        return new RouteBuilder() {
+            @Override
+            public void configure() {
+                from("direct:start").routeId("producer").to("seda:bar");
+            }
+        };
+    }
+}
diff --git 
a/core/camel-core/src/test/java/org/apache/camel/component/seda/SedaSharedQueueEndpointRestartTest.java
 
b/core/camel-core/src/test/java/org/apache/camel/component/seda/SedaSharedQueueEndpointRestartTest.java
new file mode 100644
index 000000000000..d354e0171f0b
--- /dev/null
+++ 
b/core/camel-core/src/test/java/org/apache/camel/component/seda/SedaSharedQueueEndpointRestartTest.java
@@ -0,0 +1,55 @@
+/*
+ * 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.seda;
+
+import org.apache.camel.ContextTestSupport;
+import org.apache.camel.builder.RouteBuilder;
+import org.junit.jupiter.api.Test;
+
+import static org.junit.jupiter.api.Assertions.assertDoesNotThrow;
+import static org.junit.jupiter.api.Assertions.assertEquals;
+
+class SedaSharedQueueEndpointRestartTest extends ContextTestSupport {
+
+    @Test
+    void testRestartedEndpointKeepsSharedQueueAlive() throws Exception {
+        // seda:bar and seda:bar?blockWhenFull=true are distinct endpoint 
instances sharing one queue
+        // key; a restarted endpoint must be re-added to the shared queue 
reference, otherwise stopping
+        // the sibling endpoint later removes the queue while this endpoint is 
still active
+        context.getRouteController().stopRoute("a");
+        context.getRouteController().startRoute("a");
+
+        context.getRouteController().stopRoute("b");
+
+        assertDoesNotThrow(() -> template.sendBody("direct:a", "kept-alive"),
+                "send after the sibling endpoint stopped should still find the 
shared queue");
+
+        SedaEndpoint bar = getMandatoryEndpoint("seda:bar", 
SedaEndpoint.class);
+        assertEquals(1, bar.getCurrentQueueSize());
+    }
+
+    @Override
+    protected RouteBuilder createRouteBuilder() {
+        return new RouteBuilder() {
+            @Override
+            public void configure() {
+                from("direct:a").routeId("a").to("seda:bar");
+                
from("direct:b").routeId("b").to("seda:bar?blockWhenFull=true");
+            }
+        };
+    }
+}

Reply via email to