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

markt-asf pushed a commit to branch main
in repository https://gitbox.apache.org/repos/asf/tomcat.git


The following commit(s) were added to refs/heads/main by this push:
     new 4b494c19d4 Review "Keep monitor, heartbeat and JMX alive across 
partial ... stops"
4b494c19d4 is described below

commit 4b494c19d4c1254957373266231b8433ba9c33ce
Author: Mark Thomas <[email protected]>
AuthorDate: Wed Oct 21 11:23:25 2026 +0100

    Review "Keep monitor, heartbeat and JMX alive across partial ... stops"
    
    Clean-up TLS if all services have stopped
    Keep channel on partial stop for Interceptors
---
 .../tribes/group/ChannelInterceptorBase.java       | 32 +++++++++++++++++----
 .../apache/catalina/tribes/group/GroupChannel.java |  2 +-
 .../tribes/group/TestGroupChannelStartStop.java    | 33 ++++++++++++++++++++++
 3 files changed, 60 insertions(+), 7 deletions(-)

diff --git a/java/org/apache/catalina/tribes/group/ChannelInterceptorBase.java 
b/java/org/apache/catalina/tribes/group/ChannelInterceptorBase.java
index c535233675..89a607ce18 100644
--- a/java/org/apache/catalina/tribes/group/ChannelInterceptorBase.java
+++ b/java/org/apache/catalina/tribes/group/ChannelInterceptorBase.java
@@ -187,14 +187,34 @@ public abstract class ChannelInterceptorBase implements 
ChannelInterceptor {
         if (getNext() != null) {
             getNext().stop(svc);
         }
-        if (oname != null) {
-            JmxRegistry jmxRegistry = JmxRegistry.getRegistry(channel);
-            if (jmxRegistry != null) {
-                jmxRegistry.unregisterJmx(oname);
+        // Only release the channel and the JMX registration once all services 
have been stopped. A partial stop leaves
+        // services running that still use them.
+        if (isFullyStopped()) {
+            if (oname != null) {
+                JmxRegistry jmxRegistry = JmxRegistry.getRegistry(channel);
+                if (jmxRegistry != null) {
+                    jmxRegistry.unregisterJmx(oname);
+                }
+                oname = null;
             }
-            oname = null;
+            channel = null;
         }
-        channel = null;
+    }
+
+    /**
+     * Determine if all services of the coordinator at the end of the 
interceptor stack have been stopped.
+     *
+     * @return {@code true} if no service is started, or if there is no 
coordinator after this interceptor
+     */
+    private boolean isFullyStopped() {
+        ChannelInterceptor interceptor = getNext();
+        while (interceptor != null) {
+            if (interceptor instanceof ChannelCoordinator coordinator) {
+                return coordinator.getStartLevel() == 0;
+            }
+            interceptor = interceptor.getNext();
+        }
+        return true;
     }
 
     @Override
diff --git a/java/org/apache/catalina/tribes/group/GroupChannel.java 
b/java/org/apache/catalina/tribes/group/GroupChannel.java
index 6b246de5de..81f6fb2d3b 100644
--- a/java/org/apache/catalina/tribes/group/GroupChannel.java
+++ b/java/org/apache/catalina/tribes/group/GroupChannel.java
@@ -533,7 +533,7 @@ public class GroupChannel extends ChannelInterceptorBase 
implements ManagedChann
                 heartbeatFuture.cancel(true);
                 heartbeatFuture = null;
             }
-            if ((svc & DEFAULT) == DEFAULT && sslContext != null) {
+            if (sslContext != null) {
                 sslContext.close();
                 sslContext = null;
             }
diff --git 
a/test/org/apache/catalina/tribes/group/TestGroupChannelStartStop.java 
b/test/org/apache/catalina/tribes/group/TestGroupChannelStartStop.java
index cd6f2765a4..0268deab7c 100644
--- a/test/org/apache/catalina/tribes/group/TestGroupChannelStartStop.java
+++ b/test/org/apache/catalina/tribes/group/TestGroupChannelStartStop.java
@@ -16,14 +16,17 @@
  */
 package org.apache.catalina.tribes.group;
 
+import java.util.Iterator;
 import java.util.concurrent.ScheduledFuture;
 
 import org.junit.After;
 import org.junit.Assert;
+import org.junit.Assume;
 import org.junit.Before;
 import org.junit.Test;
 
 import org.apache.catalina.tribes.Channel;
+import org.apache.catalina.tribes.ChannelInterceptor;
 import org.apache.catalina.tribes.membership.McastService;
 import org.apache.catalina.tribes.transport.ReceiverBase;
 import org.apache.catalina.tribes.transport.ReplicationTransmitter;
@@ -166,6 +169,36 @@ public class TestGroupChannelStartStop {
         Assert.assertTrue(heartbeat.isCancelled());
     }
 
+    @Test
+    public void testStagedStopReleasesSslContext() throws Exception {
+        channel.setPskIdentity("tribes-test");
+        channel.setPskKey("000102030405060708090a0b0c0d0e0f");
+        channel.start(Channel.DEFAULT);
+        // The TLS context is only available when the TLS implementation is
+        Assume.assumeNotNull(channel.getSslContext());
+        // Stop in stages: no single call stops all of the services
+        channel.stop(Channel.SND_RX_SEQ);
+        Assert.assertNotNull(channel.getSslContext());
+        channel.stop(Channel.SND_TX_SEQ | Channel.MBR_RX_SEQ | 
Channel.MBR_TX_SEQ);
+        Assert.assertNull(channel.getSslContext());
+    }
+
+    @Test
+    public void testPartialStopKeepsInterceptorChannel() throws Exception {
+        channel.start(Channel.DEFAULT);
+        channel.stop(Channel.SND_RX_SEQ);
+        // services are still running, the interceptors must still be attached 
to the channel
+        for (Iterator<ChannelInterceptor> it = channel.getInterceptors(); 
it.hasNext();) {
+            ChannelInterceptor interceptor = it.next();
+            Assert.assertSame(interceptor.getClass().getName(), channel, 
interceptor.getChannel());
+        }
+        channel.stop(Channel.SND_TX_SEQ | Channel.MBR_RX_SEQ | 
Channel.MBR_TX_SEQ);
+        for (Iterator<ChannelInterceptor> it = channel.getInterceptors(); 
it.hasNext();) {
+            ChannelInterceptor interceptor = it.next();
+            Assert.assertNull(interceptor.getClass().getName(), 
interceptor.getChannel());
+        }
+    }
+
     @Test
     public void testOverlappingPartialStart() throws Exception {
         // start one service, then start everything: the already


---------------------------------------------------------------------
To unsubscribe, e-mail: [email protected]
For additional commands, e-mail: [email protected]

Reply via email to