messere1 opened a new issue, #11352:
URL: https://github.com/apache/rocketmq/issues/11352

   ### 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
   
   OS: Windows 11 / Linux (broker module, lite topic path)
   
   ### RocketMQ version
   
   branch: develop
   Git commit id: 9ea2ccdc2b41445aee5e177e14e501d606f5a1ed (2026-10-08)
   
   ### JDK Version
   
   Compiler: Temurin JDK 17.0.19
   
   ### Describe the Bug
   
   `LiteSubscriptionRegistryImpl.excludeClientByLmqName` handles exclusive-mode 
takeover: when clientB claims a liteTopic that clientA still holds, clientA is 
evicted from that liteTopic, and when this was clientA's last liteTopic its 
`LiteSubscription` is removed from `client2Subscription` immediately 
(LiteSubscriptionRegistryImpl.java, eviction branch):
   
   ```java
   LiteSubscription liteSubscription = 
client2Subscription.get(clientGroup.clientId);
   if (liteSubscription != null) {
       liteSubscription.removeLmq(lmqName);
       // remove client if no more liteTopic
       if (liteSubscription.getLmqSet().isEmpty()) {
           client2Subscription.remove(clientGroup.clientId);
       }
   }
   ```
   
   However, the evicted client's entry in `clientChannels` is **not** removed. 
Every other removal flow goes through `removeCompleteSubscription`, which 
cleans both maps:
   
   ```java
   public void removeCompleteSubscription(String clientId) {
       clientChannels.remove(clientId);          // <-- cleaned here
       LiteSubscription thisSub = client2Subscription.remove(clientId);
       ...
   }
   ```
   
   The expiry cleaner `cleanupExpiredSubscriptions` only walks 
`client2Subscription`. Once the eviction path has removed the subscription 
directly, the orphaned `clientChannels` entry is unreachable for any cleanup: 
it is never reclaimed until the broker restarts.
   
   Impact:
   1. Under exclusive-mode client churn (every takeover evicts the previous 
client, e.g. clients restarting with new clientIds), `clientChannels` grows 
unboundedly, retaining netty `Channel` references and their associated 
resources.
   2. `notifyUnsubscribeLite` keeps resolving the stale (typically closed) 
channel for the evicted clientId.
   
   ### Steps to Reproduce
   
   1. Create a subscription group with `liteSubExclusive=true` bound to a LITE 
topic.
   2. Start clientA, subscribe (PARTIAL_ADD) to liteTopic L and register its 
channel (`updateClientChannel`).
   3. Start clientB, subscribe to the same liteTopic L (exclusive takeover). 
clientA is evicted and, since L was its only liteTopic, removed from 
`client2Subscription`.
   4. Observe that `clientChannels` still contains clientA's channel entry; it 
stays there forever because `cleanupExpiredSubscriptions` only iterates 
`client2Subscription`.
   
   A unit-level reproduction: `LiteSubscriptionRegistryImplTest` — after the 
takeover in `testAddPartialSubscription_ExclusiveMode`, 
`registry.clientChannels.get("testClient1")` still returns the channel although 
`registry.getLiteSubscription("testClient1")` is already null.
   
   ### What Did You Expect to See?
   
   When the eviction removes the evicted client's last liteTopic and drops it 
from `client2Subscription`, its channel entry should be reclaimed from 
`clientChannels` as well (like `removeCompleteSubscription` does), so 
exclusive-mode takeover churn does not accumulate stale channels.
   
   ### What Did You See Instead?
   
   The evicted client's `Channel` stays in `clientChannels` permanently; the 
map grows with every exclusive takeover whose loser had a single liteTopic.
   


-- 
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.

To unsubscribe, e-mail: [email protected]

For queries about this service, please contact Infrastructure at:
[email protected]

Reply via email to