apache / apache/rocketmq

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

Open
#11,039 1 comment 0 reactions 0 assignees View on GitHub
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

Open the contributing 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

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.