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

fmariani 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 dfd1f9a03f5a CAMEL-23194: Fix JMS shutdown deadlock that hangs CI 
builds
dfd1f9a03f5a is described below

commit dfd1f9a03f5a67929c5da583d33533e4e128a106
Author: Croway <[email protected]>
AuthorDate: Wed Apr 1 16:22:22 2026 +0200

    CAMEL-23194: Fix JMS shutdown deadlock that hangs CI builds
    
    Root cause: deadlock between BaseService.lock and Spring's
    lifecycleMonitor during CamelContext shutdown in afterAll.
    
    Thread 1 (afterAll -> context.stop()):
      AbstractCamelContext.doStop()
        -> JmsProducer.doStop()
          -> ReplyManagerSupport.doStop()
            -> DefaultMessageListenerContainer.doShutdown()
              -> Object.wait() <- blocked, waiting for listener thread
    
    Thread 2 (QueueReplyManager listener):
      DestinationResolverDelegate.resolveDestinationName()
        -> BaseService.lock <- blocked, held by Thread 1
    
    Introduced by CAMEL-20199 (Oct 2024) which migrated synchronized
    blocks to ReentrantLock. Before: BaseService.stop() used
    synchronized(lock) on a private Object, while the inner class used
    synchronized(QueueReplyManager.this) on the instance monitor — two
    independent locks. After: both became QueueReplyManager.this.lock
    (the same inherited ReentrantLock), creating the circular wait.
    
    Fix: DestinationResolverDelegate now uses a dedicated ReentrantLock
    instead of BaseService.lock, restoring the original two-lock design.
    Added volatile + fast-path for the destination field.
    
    Applied to both camel-jms and camel-sjms which share the same
    pattern via their respective QueueReplyManager classes.
---
 .../camel/component/jms/reply/QueueReplyManager.java  | 19 ++++++++++++++++---
 .../camel/component/sjms/reply/QueueReplyManager.java | 17 ++++++++++++++---
 2 files changed, 30 insertions(+), 6 deletions(-)

diff --git 
a/components/camel-jms/src/main/java/org/apache/camel/component/jms/reply/QueueReplyManager.java
 
b/components/camel-jms/src/main/java/org/apache/camel/component/jms/reply/QueueReplyManager.java
index 745ea41773d6..569d9864cd8c 100644
--- 
a/components/camel-jms/src/main/java/org/apache/camel/component/jms/reply/QueueReplyManager.java
+++ 
b/components/camel-jms/src/main/java/org/apache/camel/component/jms/reply/QueueReplyManager.java
@@ -18,6 +18,8 @@ package org.apache.camel.component.jms.reply;
 
 import java.math.BigInteger;
 import java.util.Random;
+import java.util.concurrent.locks.Lock;
+import java.util.concurrent.locks.ReentrantLock;
 
 import jakarta.jms.Destination;
 import jakarta.jms.JMSException;
@@ -98,7 +100,13 @@ public class QueueReplyManager extends ReplyManagerSupport {
 
     private final class DestinationResolverDelegate implements 
DestinationResolver {
         private final DestinationResolver delegate;
-        private Destination destination;
+        // Use a dedicated lock instead of BaseService.lock to avoid deadlock
+        // during shutdown: BaseService.stop() holds its lock while calling
+        // doStop() -> listenerContainer.destroy() -> doShutdown() which waits
+        // for listener threads to finish. If a listener thread needs to 
resolve
+        // the destination, it would deadlock trying to acquire 
BaseService.lock.
+        private final Lock destinationLock = new ReentrantLock();
+        private volatile Destination destination;
 
         DestinationResolverDelegate(DestinationResolver delegate) {
             this.delegate = delegate;
@@ -109,7 +117,12 @@ public class QueueReplyManager extends ReplyManagerSupport 
{
                 Session session, String destinationName,
                 boolean pubSubDomain)
                 throws JMSException {
-            QueueReplyManager.this.lock.lock();
+            // fast path: destination already resolved
+            Destination answer = destination;
+            if (answer != null) {
+                return answer;
+            }
+            destinationLock.lock();
             try {
                 // resolve the reply to destination
                 if (destination == null) {
@@ -117,7 +130,7 @@ public class QueueReplyManager extends ReplyManagerSupport {
                     setReplyTo(destination);
                 }
             } finally {
-                QueueReplyManager.this.lock.unlock();
+                destinationLock.unlock();
             }
             return destination;
         }
diff --git 
a/components/camel-sjms/src/main/java/org/apache/camel/component/sjms/reply/QueueReplyManager.java
 
b/components/camel-sjms/src/main/java/org/apache/camel/component/sjms/reply/QueueReplyManager.java
index c92ba21e5161..0daa079ef0f6 100644
--- 
a/components/camel-sjms/src/main/java/org/apache/camel/component/sjms/reply/QueueReplyManager.java
+++ 
b/components/camel-sjms/src/main/java/org/apache/camel/component/sjms/reply/QueueReplyManager.java
@@ -16,6 +16,9 @@
  */
 package org.apache.camel.component.sjms.reply;
 
+import java.util.concurrent.locks.Lock;
+import java.util.concurrent.locks.ReentrantLock;
+
 import jakarta.jms.Destination;
 import jakarta.jms.JMSException;
 import jakarta.jms.Message;
@@ -65,7 +68,10 @@ public class QueueReplyManager extends ReplyManagerSupport {
 
     private final class DestinationResolverDelegate implements 
DestinationCreationStrategy {
         private final DestinationCreationStrategy delegate;
-        private Destination destination;
+        // Use a dedicated lock instead of BaseService.lock to avoid deadlock
+        // during shutdown (same issue as CAMEL-23194 in camel-jms)
+        private final Lock destinationLock = new ReentrantLock();
+        private volatile Destination destination;
 
         DestinationResolverDelegate(DestinationCreationStrategy delegate) {
             this.delegate = delegate;
@@ -73,7 +79,12 @@ public class QueueReplyManager extends ReplyManagerSupport {
 
         @Override
         public Destination createDestination(Session session, String 
destinationName, boolean topic) throws JMSException {
-            QueueReplyManager.this.lock.lock();
+            // fast path: destination already resolved
+            Destination answer = destination;
+            if (answer != null) {
+                return answer;
+            }
+            destinationLock.lock();
             try {
                 // resolve the reply to destination
                 if (destination == null) {
@@ -81,7 +92,7 @@ public class QueueReplyManager extends ReplyManagerSupport {
                     setReplyTo(destination);
                 }
             } finally {
-                QueueReplyManager.this.lock.unlock();
+                destinationLock.unlock();
             }
             return destination;
         }

Reply via email to