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

lizhimins pushed a commit to branch develop
in repository https://gitbox.apache.org/repos/asf/rocketmq.git


The following commit(s) were added to refs/heads/develop by this push:
     new aea87c4e42 [ISSUE #11091] Fix remoting sub-server timeout scanning and 
scheduler shutdown (#11092)
aea87c4e42 is described below

commit aea87c4e42dbce2ce9653cb55a088031ade239f1
Author: qianye <[email protected]>
AuthorDate: Thu Sep 10 13:44:33 2026 +0800

    [ISSUE #11091] Fix remoting sub-server timeout scanning and scheduler 
shutdown (#11092)
---
 .../remoting/netty/NettyRemotingServer.java        |   5 +-
 .../netty/NettyRemotingServerLifecycleTest.java    | 111 +++++++++++++++++++++
 2 files changed, 115 insertions(+), 1 deletion(-)

diff --git 
a/remoting/src/main/java/org/apache/rocketmq/remoting/netty/NettyRemotingServer.java
 
b/remoting/src/main/java/org/apache/rocketmq/remoting/netty/NettyRemotingServer.java
index ab2e208f48..64a1eb42d0 100644
--- 
a/remoting/src/main/java/org/apache/rocketmq/remoting/netty/NettyRemotingServer.java
+++ 
b/remoting/src/main/java/org/apache/rocketmq/remoting/netty/NettyRemotingServer.java
@@ -265,7 +265,9 @@ public class NettyRemotingServer extends 
NettyRemotingAbstract implements Remoti
             @Override
             public void run(Timeout timeout) {
                 try {
-                    NettyRemotingServer.this.scanResponseTable();
+                    for (NettyRemotingAbstract server : 
remotingServerTable.values()) {
+                        server.scanResponseTable();
+                    }
                 } catch (Throwable e) {
                     log.error("scanResponseTable exception", e);
                 } finally {
@@ -334,6 +336,7 @@ public class NettyRemotingServer extends 
NettyRemotingAbstract implements Remoti
             }
 
             this.timer.stop();
+            this.scheduledExecutorService.shutdown();
 
             this.eventLoopGroupBoss.shutdownGracefully();
 
diff --git 
a/remoting/src/test/java/org/apache/rocketmq/remoting/netty/NettyRemotingServerLifecycleTest.java
 
b/remoting/src/test/java/org/apache/rocketmq/remoting/netty/NettyRemotingServerLifecycleTest.java
new file mode 100644
index 0000000000..472ed77c30
--- /dev/null
+++ 
b/remoting/src/test/java/org/apache/rocketmq/remoting/netty/NettyRemotingServerLifecycleTest.java
@@ -0,0 +1,111 @@
+/*
+ * 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.rocketmq.remoting.netty;
+
+import io.netty.channel.Channel;
+import io.netty.channel.DefaultChannelPromise;
+import io.netty.util.concurrent.ImmediateEventExecutor;
+import java.net.InetSocketAddress;
+import java.util.Arrays;
+import java.util.concurrent.CompletableFuture;
+import java.util.concurrent.ScheduledExecutorService;
+import java.util.concurrent.TimeUnit;
+import org.apache.commons.lang3.reflect.FieldUtils;
+import org.apache.rocketmq.remoting.RemotingServer;
+import org.apache.rocketmq.remoting.exception.RemotingTimeoutException;
+import org.apache.rocketmq.remoting.protocol.RemotingCommand;
+import org.junit.After;
+import org.junit.Before;
+import org.junit.Test;
+
+import static org.assertj.core.api.Assertions.assertThat;
+import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.when;
+
+public class NettyRemotingServerLifecycleTest {
+    private NettyRemotingServer server;
+    private ScheduledExecutorService scheduler;
+
+    @Before
+    public void setUp() throws Exception {
+        NettyServerConfig config = new NettyServerConfig();
+        config.setBindAddress("127.0.0.1");
+        config.setListenPort(0);
+        config.setServerSelectorThreads(1);
+        config.setServerWorkerThreads(1);
+        config.setServerCallbackExecutorThreads(1);
+        config.setServerAsyncSemaphoreValue(2);
+        server = new NettyRemotingServer(config);
+        scheduler = (ScheduledExecutorService) 
FieldUtils.readDeclaredField(server, "scheduledExecutorService", true);
+        server.start();
+    }
+
+    @After
+    public void tearDown() throws Exception {
+        // Also clean up the scheduler when the shutdown regression test fails 
on unfixed code.
+        if (scheduler != null) {
+            scheduler.shutdownNow();
+        }
+        if (server != null) {
+            server.shutdown();
+            server.eventLoopGroupBoss.terminationFuture().await(5, 
TimeUnit.SECONDS);
+            server.eventLoopGroupSelector.terminationFuture().await(5, 
TimeUnit.SECONDS);
+            if (server.getDefaultEventExecutorGroup() != null) {
+                
server.getDefaultEventExecutorGroup().terminationFuture().await(5, 
TimeUnit.SECONDS);
+            }
+            server.getCallbackExecutor().awaitTermination(5, TimeUnit.SECONDS);
+        }
+    }
+
+    @Test
+    public void testScanTimeoutsForParentAndSubServers() throws Exception {
+        // No extra listeners are needed: server-initiated requests use an 
existing channel.
+        RemotingServer firstSubServer = server.newRemotingServer(1234);
+        RemotingServer secondSubServer = server.newRemotingServer(1235);
+        Channel channel = mock(Channel.class);
+        when(channel.remoteAddress()).thenReturn(new 
InetSocketAddress("127.0.0.1", 4321));
+        when(channel.writeAndFlush(any())).thenAnswer(invocation ->
+            new DefaultChannelPromise(channel, 
ImmediateEventExecutor.INSTANCE).setSuccess());
+
+        for (RemotingServer target : Arrays.asList(server, firstSubServer, 
secondSubServer)) {
+            NettyRemotingAbstract remoting = (NettyRemotingAbstract) target;
+            CompletableFuture<ResponseFuture> expired = new 
CompletableFuture<>();
+            RemotingCommand request = RemotingCommand.createRequestCommand(0, 
null);
+            target.invokeAsync(channel, request, 100, expired::complete);
+            CompletableFuture<ResponseFuture> pending = 
remoting.invokeImpl(channel,
+                RemotingCommand.createRequestCommand(0, null), 
TimeUnit.MINUTES.toMillis(1));
+            assertThat(remoting.responseTable).hasSize(2);
+            assertThat(remoting.semaphoreAsync.availablePermits()).isZero();
+
+            ResponseFuture response = expired.get(10, TimeUnit.SECONDS);
+            
assertThat(response.getCause()).isInstanceOf(RemotingTimeoutException.class);
+            
assertThat(remoting.responseTable).hasSize(1).doesNotContainKey(request.getOpaque());
+            
assertThat(remoting.semaphoreAsync.availablePermits()).isEqualTo(1);
+            assertThat(pending.isDone()).isFalse();
+        }
+    }
+
+    @Test
+    public void testShutdownStopsScheduler() throws Exception {
+        // Ensure its worker has started before checking termination.
+        scheduler.submit(() -> { }).get(5, TimeUnit.SECONDS);
+        server.shutdown();
+        assertThat(scheduler.isShutdown()).isTrue();
+        assertThat(scheduler.awaitTermination(5, TimeUnit.SECONDS)).isTrue();
+    }
+}

Reply via email to