[Bug] Consumers registered via heartbeat-v2 withoutSub are never removed by fast channel-close events
- Dominant language
- Java
- Stars
- 22.6k
- Forks
- 12k
- Avg merge
- 3d 1h
- Merged PRs (30d)
- 27
Description
### Before Creating the Bug Report
- [X] I found a bug, not just asking a question, which should be created in [GitHub Discussions](https://github.com/apache/rocketmq/discussions).
- [X] I have searched the [GitHub Issues](https://github.com/apache/rocketmq/issues) and [GitHub Discussions](https://github.com/apache/rocketmq/discussions) of this repository and believe that this is not a duplicate.
- [X] 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).
Contributor guide
Research direction
Start in ConsumerManager.java with registerConsumerWithoutSub, registerConsumer, and doChannelCloseEvent, then review fix PR #11040 and its regression test. Confirm the test fails before the fix and passes afterward, and that a fast channel-close removes registrations created through the heartbeat-v2 withoutSub path.
Written by the indexing model from the issue text.
Assessment
- Tech stack
- java
- Domain
- backend, distributed-systems
- Issue type
- Bug
- Difficulty
- 3/5
- Estimated time
- 1-2 days
- Activity status
- Stale
- Clarity
- Clearly specified
- Newbie friendliness
- 25/100