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

fuyou001 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 592d59d12d [ISSUE #11493] Avoid retaining channels for orphan 
unregister (#11494)
592d59d12d is described below

commit 592d59d12db356f8c98332d9e355a89d92590db3
Author: qianye <[email protected]>
AuthorDate: Tue Oct 27 17:01:03 2026 +0800

    [ISSUE #11493] Avoid retaining channels for orphan unregister (#11494)
    
    * fix(proxy): avoid retaining channels for orphan unregister
    
    Only cache remote channels for REGISTER heartbeats so an unmatched
    UNREGISTER cannot leave an unreachable entry in remoteChannelMap.
    
    * fix(proxy): preserve unregister delivery for uncached channels
    
    Use the decoded channel without caching it when an UNREGISTER arrives
    for a channel absent from remoteChannelMap, so ConsumerManager can
    still perform its normal cleanup.
---
 .../proxy/service/sysmessage/HeartbeatSyncer.java  |  8 +++++-
 .../service/sysmessage/HeartbeatSyncerTest.java    | 32 ++++++++++++++++++++++
 2 files changed, 39 insertions(+), 1 deletion(-)

diff --git 
a/proxy/src/main/java/org/apache/rocketmq/proxy/service/sysmessage/HeartbeatSyncer.java
 
b/proxy/src/main/java/org/apache/rocketmq/proxy/service/sysmessage/HeartbeatSyncer.java
index 5d9fbb3069..9703c95fb9 100644
--- 
a/proxy/src/main/java/org/apache/rocketmq/proxy/service/sysmessage/HeartbeatSyncer.java
+++ 
b/proxy/src/main/java/org/apache/rocketmq/proxy/service/sysmessage/HeartbeatSyncer.java
@@ -202,7 +202,13 @@ public class HeartbeatSyncer extends 
AbstractSystemMessageSyncer {
                 }
 
                 RemoteChannel decodedChannel = 
RemoteChannel.decode(data.getChannelData());
-                RemoteChannel channel = 
remoteChannelMap.computeIfAbsent(buildKey(data.getGroup(), decodedChannel), key 
-> decodedChannel);
+                String key = buildKey(data.getGroup(), decodedChannel);
+                RemoteChannel channel;
+                if (data.getHeartbeatType().equals(HeartbeatType.REGISTER)) {
+                    channel = remoteChannelMap.computeIfAbsent(key, k -> 
decodedChannel);
+                } else {
+                    channel = remoteChannelMap.getOrDefault(key, 
decodedChannel);
+                }
                 
channel.setExtendAttribute(decodedChannel.getChannelExtendAttribute());
                 ClientChannelInfo clientChannelInfo = new ClientChannelInfo(
                     channel,
diff --git 
a/proxy/src/test/java/org/apache/rocketmq/proxy/service/sysmessage/HeartbeatSyncerTest.java
 
b/proxy/src/test/java/org/apache/rocketmq/proxy/service/sysmessage/HeartbeatSyncerTest.java
index 9a2c5e3437..d322502b64 100644
--- 
a/proxy/src/test/java/org/apache/rocketmq/proxy/service/sysmessage/HeartbeatSyncerTest.java
+++ 
b/proxy/src/test/java/org/apache/rocketmq/proxy/service/sysmessage/HeartbeatSyncerTest.java
@@ -217,6 +217,38 @@ public class HeartbeatSyncerTest extends InitConfigTest {
         assertSame(channelInfoList.get(0).getChannel(), 
syncUnRegisterChannelInfoArgumentCaptor.getValue().getChannel());
     }
 
+    @Test
+    public void testSyncUnregisterWithoutRegister() throws Exception {
+        String consumerGroup = "consumerGroup";
+        GrpcClientSettingsManager grpcClientSettingsManager = 
mock(GrpcClientSettingsManager.class);
+        GrpcChannelManager grpcChannelManager = mock(GrpcChannelManager.class);
+        GrpcClientChannel grpcClientChannel = new GrpcClientChannel(
+            proxyRelayService, grpcClientSettingsManager, grpcChannelManager,
+            
ProxyContext.create().setRemoteAddress(remoteAddress).setLocalAddress(localAddress),
+            clientId);
+        ClientChannelInfo clientChannelInfo = new ClientChannelInfo(
+            grpcClientChannel,
+            clientId,
+            LanguageCode.JAVA,
+            5
+        );
+        HeartbeatSyncer heartbeatSyncer = new 
HeartbeatSyncer(topicRouteService, adminService, consumerManager, 
mqClientAPIFactory, null);
+
+        ArgumentCaptor<Message> messageArgumentCaptor = 
ArgumentCaptor.forClass(Message.class);
+        SendResult sendResult = new SendResult();
+        sendResult.setSendStatus(SendStatus.SEND_OK);
+        
doReturn(CompletableFuture.completedFuture(sendResult)).when(this.mqClientAPIExt)
+            .sendMessageAsync(anyString(), anyString(), 
messageArgumentCaptor.capture(), any(), anyLong());
+        heartbeatSyncer.onConsumerUnRegister(consumerGroup, clientChannelInfo);
+        await().atMost(Duration.ofSeconds(3)).until(() -> 
!messageArgumentCaptor.getAllValues().isEmpty());
+
+        heartbeatSyncer.localProxyId = RandomStringUtils.randomAlphabetic(10);
+        
heartbeatSyncer.consumeMessage(Lists.newArrayList(convertFromMessage(messageArgumentCaptor.getValue())),
 null);
+
+        assertTrue(heartbeatSyncer.remoteChannelMap.isEmpty());
+        verify(consumerManager).unregisterConsumer(eq(consumerGroup), any(), 
eq(false));
+    }
+
     @Test
     public void testSyncRemotingChannel() throws Exception {
         String consumerGroup = "consumerGroup";

Reply via email to