This is an automated email from the ASF dual-hosted git repository.
lollipopjin 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 923a80e9f4 [ISSUE #11161] Fix closeChannel table eviction and
ChannelWrapper.close lock ordering (#11162)
923a80e9f4 is described below
commit 923a80e9f49cf79b3424efb3228c602afb46c35b
Author: qianye <[email protected]>
AuthorDate: Mon Sep 14 17:19:37 2026 +0800
[ISSUE #11161] Fix closeChannel table eviction and ChannelWrapper.close
lock ordering (#11162)
---
.../remoting/netty/NettyRemotingClient.java | 27 ++++---
.../netty/NettyRemotingClientCloseChannelTest.java | 82 ++++++++++++++++++++++
2 files changed, 100 insertions(+), 9 deletions(-)
diff --git
a/remoting/src/main/java/org/apache/rocketmq/remoting/netty/NettyRemotingClient.java
b/remoting/src/main/java/org/apache/rocketmq/remoting/netty/NettyRemotingClient.java
index 627d6255f5..4ba18b53d9 100644
---
a/remoting/src/main/java/org/apache/rocketmq/remoting/netty/NettyRemotingClient.java
+++
b/remoting/src/main/java/org/apache/rocketmq/remoting/netty/NettyRemotingClient.java
@@ -436,7 +436,7 @@ public class NettyRemotingClient extends
NettyRemotingAbstract implements Remoti
if (null == prevCW) {
LOGGER.info("closeChannel: the channel[addr={}, id={}]
has been removed from the channel table before", addrRemote, channel.id());
removeItemFromTable = false;
- } else if (prevCW.isWrapperOf(channel)) {
+ } else if (!prevCW.isWrapperOf(channel)) {
LOGGER.info("closeChannel: the channel[addr={}, id={}]
has been closed before, and has been created again, nothing to do.",
addrRemote, channel.id());
removeItemFromTable = false;
@@ -1087,16 +1087,25 @@ public class NettyRemotingClient extends
NettyRemotingAbstract implements Remoti
}
public void close() {
+ // Snapshot the channels under the read lock, then close them
without holding
+ // any wrapper lock. Holding the wrapper lock across closeChannel
would take
+ // lockChannelTables while inside the wrapper lock, which inverts
the
+ // lockChannelTables -> wrapper lock order used by
createChannelAsync and
+ // closeChannel (via tryClose).
+ ChannelFuture current;
+ ChannelFuture toClose;
+ lock.readLock().lock();
try {
- lock.writeLock().lock();
- if (channelFuture != null) {
- closeChannel(channelFuture.channel());
- }
- if (channelToClose != null) {
- closeChannel(channelToClose.channel());
- }
+ current = this.channelFuture;
+ toClose = this.channelToClose;
} finally {
- lock.writeLock().unlock();
+ lock.readLock().unlock();
+ }
+ if (current != null) {
+ closeChannel(channelAddress, current.channel());
+ }
+ if (toClose != null) {
+ closeChannel(channelAddress, toClose.channel());
}
}
}
diff --git
a/remoting/src/test/java/org/apache/rocketmq/remoting/netty/NettyRemotingClientCloseChannelTest.java
b/remoting/src/test/java/org/apache/rocketmq/remoting/netty/NettyRemotingClientCloseChannelTest.java
new file mode 100644
index 0000000000..dd51fde079
--- /dev/null
+++
b/remoting/src/test/java/org/apache/rocketmq/remoting/netty/NettyRemotingClientCloseChannelTest.java
@@ -0,0 +1,82 @@
+/*
+ * 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.ChannelFuture;
+import io.netty.channel.DefaultChannelPromise;
+import io.netty.channel.embedded.EmbeddedChannel;
+import io.netty.util.concurrent.ImmediateEventExecutor;
+import java.util.Map;
+import org.apache.commons.lang3.reflect.FieldUtils;
+import org.junit.After;
+import org.junit.Before;
+import org.junit.Test;
+
+import static org.assertj.core.api.Assertions.assertThat;
+
+public class NettyRemotingClientCloseChannelTest {
+ private static final String ADDR = "127.0.0.1:9876";
+ private NettyRemotingClient client;
+
+ @Before
+ public void setUp() {
+ client = new NettyRemotingClient(new NettyClientConfig());
+ }
+
+ @After
+ public void tearDown() {
+ client.shutdown();
+ }
+
+ @SuppressWarnings("unchecked")
+ private Map<String, NettyRemotingClient.ChannelWrapper> channelTables()
throws Exception {
+ return (Map<String, NettyRemotingClient.ChannelWrapper>)
FieldUtils.readField(client, "channelTables", true);
+ }
+
+ private ChannelFuture activeChannelFuture() {
+ EmbeddedChannel channel = new EmbeddedChannel();
+ DefaultChannelPromise promise = new DefaultChannelPromise(channel,
ImmediateEventExecutor.INSTANCE);
+ promise.setSuccess();
+ return promise;
+ }
+
+ @Test
+ public void testCloseChannelRemovesEntryWhenWrapperMatchesChannel() throws
Exception {
+ ChannelFuture future = activeChannelFuture();
+ NettyRemotingClient.ChannelWrapper wrapper = client.new
ChannelWrapper(ADDR, future);
+ channelTables().put(ADDR, wrapper);
+
+ client.closeChannel(ADDR, future.channel());
+
+ assertThat(channelTables()).doesNotContainKey(ADDR);
+ }
+
+ @Test
+ public void
testCloseChannelKeepsEntryWhenWrapperRecreatedForAnotherChannel() throws
Exception {
+ // The table holds the wrapper for a freshly recreated channel.
Closing an older,
+ // unrelated channel for the same address must not evict the current
entry.
+ ChannelFuture current = activeChannelFuture();
+ NettyRemotingClient.ChannelWrapper wrapper = client.new
ChannelWrapper(ADDR, current);
+ channelTables().put(ADDR, wrapper);
+
+ ChannelFuture stale = activeChannelFuture();
+ client.closeChannel(ADDR, stale.channel());
+
+ assertThat(channelTables()).containsKey(ADDR);
+ assertThat(channelTables().get(ADDR)).isSameAs(wrapper);
+ }
+}