#11039·rocketmq

[Bug] Consumers registered via heartbeat-v2 withoutSub are never removed by fast channel-close events

Author: unbridled-41Created Sep 5, 2026Updated Sep 17, 2026

Before Creating the Bug Report

  • I found a bug, not just asking a question, which should be created in GitHub Discussions.
  • I have searched the GitHub Issues and GitHub Discussions of this repository and believe that this is not a duplicate.
  • I have confirmed that this bug belongs to the current repository, not other repositories of RocketMQ.

Runtime platform environment

Linux, JDK 21, develop (ff8f6f74c)

RocketMQ version

5.x develop

Describe the Bug

With enableFastChannelEventProcess=true, channel-close cleanup is driven by the CHANNEL_CONSUMER_GROUP channel attribute that ConsumerManager#registerConsumer writes via ClientChannelAttributeHelper.addConsumerGroup(...) (ConsumerManager.java, the r1 branch).

ConsumerManager#registerConsumerWithoutSub — the path taken by heartbeat-v2 when the client's subscription fingerprint is unchanged (withoutSub=true) — also registers the channel in consumerTable via updateChannel, but never calls addConsumerGroup. Two consequences:

  1. A consumer whose registration on a channel arrives only via a withoutSub heartbeat (the normal case for a reconnecting client whose subscriptions did not change — e.g. broker restart or idle connection drop, MQClientInstance sends withoutSub=true on the new channel once the fingerprint matches) has no channel attribute. doChannelCloseEvent's fast path iterates ClientChannelAttributeHelper.getConsumerGroups(channel), gets an empty list, and returns without removing anything.
  2. A later full heartbeat on the same channel cannot repair this: updateChannel returns false for an already-registered channel, so registerConsumer's r1-guarded addConsumerGroup is never reached.

The dead consumer entry then stays in consumerTable for up to channelExpiredTimeout (120s by default) until scanNotActiveChannel expires it, delaying UNREGISTER notifications (ConsumerFilterManager cleanup) and leaving stale entries in getConsumerConnectionList / consumer connection queries.

Steps to Reproduce

  1. Enable enableFastChannelEventProcess.
  2. Register a consumer group on a channel via registerConsumerWithoutSub (heartbeat-v2 withoutSub).
  3. Close the channel and fire doChannelCloseEvent.
  4. The group is still present in consumerTable (returns false instead of true).

Expected Behavior

The channel-close fast path must remove consumer registrations whose registration came through registerConsumerWithoutSub, i.e. that method must maintain the same channel attribute as registerConsumer does.

Corresponding PR

  • Fix PR: #11040 (linked with Closes #11039 in the PR description; contains the regression test that fails before the fix and passes after it).