Aias00 opened a new issue, #3539:
URL: https://github.com/apache/dubbo-go/issues/3539

   ### Problem
   
   Two bugs in the Zookeeper config_center listener:
   
   1. **`RemoveListener` uses a different key than `AddListener` — listener 
leak.** `AddListener` qualifies the key (`namespace/key` → `buildPath(rootPath, 
...)`) before storing under the qualified key, but `RemoveListener` passes the 
**raw** caller key, so `CacheListener.RemoveListener` does 
`keyListeners.Load(rawKey)` which never matches the stored qualified key → 
`loaded` is false → the listener is never deleted. Every remove is a silent 
no-op; stale listeners accumulate and keep receiving `DataChange` after 
unsubscribe.
   
      ```go
      // config_center/zookeeper/impl.go
      func (c *zookeeperDynamicConfiguration) AddListener(key string, listener 
..., ...) {
          key = 
strings.Join([]string{c.GetURL().GetParam(constant.ConfigNamespaceKey, ...), 
key}, "/")
          qualifiedKey := buildPath(c.rootPath, key)
          c.cacheListener.AddListener(qualifiedKey, listener)   // stored 
qualified
      }
      func (c *zookeeperDynamicConfiguration) RemoveListener(key string, 
listener ..., ...) {
          c.cacheListener.RemoveListener(key, listener)           // looked up 
RAW -> no-op
      }
      ```
   
   2. **`CacheListener` inner listener map has no lock — fatal concurrent map 
read+write.** `keyListeners` is a `sync.Map` whose **value** is a plain 
`map[ConfigurationListener]struct{}` with no mutex (unlike 
`metadata/report/zookeeper`'s locked `ListenerSet`). 
`AddListener`/`RemoveListener` (router/config goroutines) write/delete the 
inner map; `DataChange` (zk event goroutine) ranges it. Concurrent map 
read+write is a fatal Go runtime error (`fatal error: concurrent map read and 
map write`), not just a benign race.
   
   ### Current behavior
   
   - Router `RemoveListener` calls (condition/script/affinity routers all call 
`dynamicConfiguration.RemoveListener(key, ...)`) are no-ops → leaked stale 
listeners keep firing `Process` against destroyed routers.
   - A config push arriving while a router subscribes/unsubscribes crashes the 
process with a fatal concurrent-map-access error.
   
   ### Expected behavior
   
   1. `RemoveListener` must qualify the key identically to `AddListener` so the 
stored entry is actually removed.
   2. The inner listener set must be guarded by a mutex (or replaced with a 
locked set), and `DataChange` must not hold that lock across the external 
`Process` callback.
   
   ### Suggested approach
   
   - Add a `qualifyKey(key)` helper used by **both** `AddListener` and 
`RemoveListener` so the two keys cannot drift.
   - Replace the inner `map[ConfigurationListener]struct{}` value with a 
mutex-guarded `listenerSet` (`add`/`remove`/`snapshot`); `DataChange` snapshots 
under the lock and dispatches `Process` outside the lock.
   
   ### Acceptance criteria
   
   - [ ] `RemoveListener` removes the listener that `AddListener` stored (same 
qualified key).
   - [ ] The inner listener set is mutex-guarded; no fatal 
concurrent-map-access under `-race`.
   - [ ] `DataChange` dispatches `Process` outside the set lock.
   - [ ] Regression tests: `qualifyKey` equality between Add/Remove; 
`listenerSet` concurrency under `-race`.
   


-- 
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]


---------------------------------------------------------------------
To unsubscribe, e-mail: [email protected]
For additional commands, e-mail: [email protected]

Reply via email to