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

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

commit df17fd2e9b60ebe497e02618824bd8d44c560394
Author: opencode <[email protected]>
AuthorDate: Sun Oct 11 10:41:52 2026 +0200

    Protect the multicast shutdown send so that stop completes
    
    When the last service level was stopped, the SHUTDOWN broadcast was
    sent outside the protection that the surrounding leaveGroup and
    close calls enjoy. The send routinely fails in exactly the
    circumstances that accompany a shutdown, a down network device or an
    already broken socket, and its IOException then skipped leaving the
    multicast group and closing the socket, and kept the facade from
    releasing its implementation, leaking the socket and the group
    membership for the rest of the JVM's life. A recovery would retry the
    close on the next attempt, but a plain cluster shutdown has no retry.
    
    The send is now wrapped like its neighbours, logging at debug while
    the shutdown message simply does not reach the peers, as it already
    did not, and they expire the member on the drop time as usual.
    
    Add a test that breaks the socket before stopping and checks that the
    implementation is released, which fails on the previous code.
---
 .../tribes/membership/McastServiceImpl.java        |  9 ++-
 .../tribes/membership/TestMcastService.java        | 72 ++++++++++++++++++++++
 2 files changed, 80 insertions(+), 1 deletion(-)

diff --git a/java/org/apache/catalina/tribes/membership/McastServiceImpl.java 
b/java/org/apache/catalina/tribes/membership/McastServiceImpl.java
index d5c538026f..24b2b99a19 100644
--- a/java/org/apache/catalina/tribes/membership/McastServiceImpl.java
+++ b/java/org/apache/catalina/tribes/membership/McastServiceImpl.java
@@ -342,7 +342,14 @@ public class McastServiceImpl extends 
MembershipProviderBase {
         if (startLevel == 0) {
             // send a stop message
             member.setCommand(Member.SHUTDOWN_PAYLOAD);
-            send(false);
+            try {
+                send(false);
+            } catch (Exception e) {
+                // Shutting down. Only log at debug.
+                if (log.isDebugEnabled()) {
+                    log.debug(sm.getString("mcastServiceImpl.error.stop"), e);
+                }
+            }
             // leave mcast group
             try {
                 socket.leaveGroup(new InetSocketAddress(address, 0), null);
diff --git a/test/org/apache/catalina/tribes/membership/TestMcastService.java 
b/test/org/apache/catalina/tribes/membership/TestMcastService.java
new file mode 100644
index 0000000000..ad5b981db5
--- /dev/null
+++ b/test/org/apache/catalina/tribes/membership/TestMcastService.java
@@ -0,0 +1,72 @@
+/*
+ * 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.catalina.tribes.membership;
+
+import java.io.ByteArrayInputStream;
+import java.util.Properties;
+
+import org.junit.Assert;
+import org.junit.Test;
+
+import org.apache.catalina.tribes.Channel;
+import org.apache.catalina.tribes.group.GroupChannel;
+
+public class TestMcastService {
+
+    /*
+     * The shutdown message that stop() broadcasts routinely fails when the 
network device is down
+     * or the socket is already broken. That must not skip leaving the 
multicast group and closing
+     * the socket, nor prevent the service facade from releasing its 
implementation.
+     */
+    @Test(timeout = 60000)
+    public void testStopWithFailingShutdownSendCompletes() throws Exception {
+        int base = 40000 + (int) Math.floorMod(System.nanoTime(), 10000);
+        StringBuilder properties = new StringBuilder();
+        properties.append("mcastAddress=228.0.0.4\n");
+        properties.append("mcastPort=").append(base).append("\n");
+        properties.append("tcpListenHost=127.0.0.1\n");
+        properties.append("tcpListenPort=").append(base + 1).append("\n");
+        properties.append("tcpSecurePort=0\n");
+        properties.append("udpListenPort=0\n");
+        properties.append("mcastFrequency=100\n");
+        properties.append("memberDropTime=3000\n");
+        properties.append("recoveryEnabled=false\n");
+        properties.append("recoveryCounter=10\n");
+        properties.append("recoverySleepTime=3000\n");
+
+        GroupChannel channel = new GroupChannel();
+        channel.setName("TestMcastService-" + System.nanoTime());
+
+        McastService service = new McastService();
+        service.setChannel(channel);
+        Properties props = new Properties();
+        props.load(new 
ByteArrayInputStream(properties.toString().getBytes("UTF-8")));
+        service.setProperties(props);
+        service.start(Channel.MBR_TX_SEQ);
+
+        McastServiceImpl impl = service.impl;
+        Assert.assertNotNull(impl);
+
+        // Break the socket so that the shutdown message cannot be sent
+        impl.socket.close();
+
+        // Stop must complete and release the implementation despite the 
failing shutdown send
+        service.stop(Channel.MBR_TX_SEQ);
+
+        Assert.assertNull("the membership implementation must be released on 
stop", service.impl);
+    }
+}


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

Reply via email to