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

   ## Summary
   
   This issue tracks potential bugs found through static analysis (code review, 
`go vet`, pattern matching, and manual verification) of the production code 
(non-test files) in the `cluster`, `config_center`, `protocol`, `registry`, 
`common`, `remoting`, and `graceful_shutdown` packages.
   
   Findings are grouped by severity:
   - **P0 (7)** — can cause process crash (unrecoverable `fatal error`), 
goroutine leaks, or fd exhaustion under normal operation
   - **P1 (10)** — high-risk: swallowed errors, unchecked type assertions, 
missing cancellation
   - **P2 (7)** — robustness / anti-pattern improvements
   
   All line numbers refer to the current `develop` tip.
   
   ---
   
   ## 🔴 P0 — Must Fix
   
   ### 1. Concurrent map read/write in ZooKeeper `CacheListener` → fatal crash
   - **File:** `config_center/zookeeper/listener.go:48-91`
   - **Code:**
     ```go
     // AddListener
     listeners, loaded := l.keyListeners.LoadOrStore(key, 
map[...]struct{}{listener: {}})
     if loaded {
         listeners.(map[...]struct{})[listener] = struct{}{}  // writes inner 
map
         l.keyListeners.Store(key, listeners)
     }
     // DataChange
     if listeners, ok := l.keyListeners.Load(event.Path); ok {
         for listener := range listeners.(map[...]struct{}) { ... }  // reads 
inner map
     }
     ```
   - **Problem:** `keyListeners` is a `sync.Map`, but the stored value is a 
plain `map`. `AddListener`/`RemoveListener` mutate the inner map directly while 
`DataChange` may `range` over the same map concurrently. This triggers Go's 
built-in concurrent-map detector → `fatal error: concurrent map read and map 
write`, which **cannot be recovered** and kills the process.
   - **Fix:** Protect the inner map with its own `sync.RWMutex`, or replace 
with copy-on-write (build a new map and `Store` it atomically).
   
   ### 2. Goroutine leak in `ForkingClusterInvoker`
   - **File:** `cluster/cluster/forking/cluster_invoker.go:74-99`
   - **Problem:** A `queue.New(1)` (capacity 1) collects results. N fork 
goroutines each call `resultQ.Put(result)`. The caller does a single 
`resultQ.Poll(1, timeout)` then returns. The remaining `N-1` fork goroutines 
block forever on `Put` (queue full, never drained, never closed) → **leaks N-1 
goroutines per invocation**.
   - **Fix:** Use a buffered channel `chan result.Result` with cap = 
`len(selected)`; use `select { case ch <- r: default: }` for non-blocking send, 
and `context`-cancel the goroutines on exit.
   
   ### 3. Type-assertion panic in Apollo `makeNestedMap`
   - **File:** `config_center/apollo/impl.go:211`
   - **Code:** `current = current[part].(map[string]any)`
   - **Problem:** If a config key is both a leaf and a parent (e.g. `a.b=1` and 
`a.b.c=2`), `current[part]` is a `string`, and the assertion panics. No `ok` 
check.
   - **Fix:** `next, ok := current[part].(map[string]any); if !ok { next = 
make(map[string]any); current[part] = next }; current = next`
   
   ### 4. Type-assertion panic in Apollo `GetInternalProperty`
   - **File:** `config_center/apollo/impl.go:121`
   - **Code:** `return value.(string), nil`
   - **Problem:** `cache.Get(key)` returns `interface{}`; a non-string cached 
value (e.g. a number or nested map) panics on the assertion.
   - **Fix:** `s, ok := value.(string); if !ok { return "", perrors.New(...) }`
   
   ### 5. File-descriptor leak in `cache_manager.loadCache`
   - **File:** `registry/servicediscovery/store/cache_manager.go:106-127`
   - **Problem:** On decode failure the function `return err` **before** 
`cf.Close()`, leaking the open file. Only the success path closes it. Missing 
`defer cf.Close()`.
   - **Fix:** `cf, err := os.Open(...); if err != nil { return err }; defer 
cf.Close()`
   
   ### 6. File-descriptor leak in `accesslog.openLogFile`
   - **File:** `filter/accesslog/filter.go:321-349`
   - **Problem:** On `Stat` failure / `Rename` failure / log-rotation paths, 
the freshly `OpenFile`'d `*os.File` is not closed → fd leak that accumulates 
over long-running services.
   - **Fix:** Ensure `defer f.Close()` (or close on every error branch) before 
reassigning the package-level file handle.
   
   ### 7. Goroutine leak in `FailbackClusterInvoker.process`
   - **File:** `cluster/cluster/failback/cluster_invoker.go:94-121`
   - **Problem:** `for range ticker.C { ... }` has no `select { case 
<-ctx.Done(): ... }` exit; `ticker` is never `Stop()`-ed via `defer`. The 
goroutine only exits when `taskList.Dispose()` is called.
   - **Fix:** Add `defer ticker.Stop()` and a `case <-ctx.Done(): return` 
branch.
   
   ---
   
   ## 🟡 P1 — High Risk
   
   ### 8. Subscription goroutine has no exit mechanism
   - **File:** `registry/directory/directory.go:205-209`
   - **Code:** `go func() { if err := dir.registry.Subscribe(url, dir); err != 
nil { ... } }()`
   - **Problem:** No `context` bound to the goroutine lifecycle. If `Subscribe` 
blocks, `Destroy` cannot terminate it → goroutine leak.
   - **Fix:** Pass a cancellable `ctx`; `cancel()` on `Destroy`.
   
   ### 9. Hessian encode errors silently ignored (request)
   - **File:** `protocol/dubbo/hessian2/hessian_request.go:129,151,153,168`
   - **Problem:** `_, _ = encoder.Encode(...)` discards errors; an encoding 
failure still sends a truncated frame, causing hard-to-diagnose decode errors 
on the peer side.
   
   ### 10. Hessian encode errors silently ignored (impl)
   - **File:** `protocol/dubbo/impl/hessian.go:128` (and surrounding)
   - **Problem:** Same class of issue as #9 in the legacy encoder path.
   
   ### 11. Triple handler registration failure swallowed
   - **File:** `protocol/triple/server.go:546`
   - **Problem:** `RegisterHandler` failure is logged but not propagated; 
service appears "up" while actually broken.
   
   ### 12. gRPC `ReflectResponse` error ignored
   - **File:** `protocol/grpc/grpc_invoker.go:128`
   - **Problem:** Error from `ReflectResponse` is ignored; malformed responses 
go undetected.
   
   ### 13. Unchecked type assertion (registry protocol)
   - **File:** `registry/protocol/protocol.go:97`
   - **Problem:** Type assertion without `ok`; wrong config type → panic.
   
   ### 14. Unchecked type assertion (registry directory)
   - **File:** `registry/directory/directory.go:502`
   - **Problem:** Type assertion without `ok` on a value pulled from a map.
   
   ### 15. `base_registry` retry loop has no ctx exit
   - **File:** `registry/base_registry.go:330`
   - **Problem:** Retry loop continues without honoring `context` cancellation 
→ can block shutdown.
   
   ### 16. `getty` pool `time.Sleep` ignores cancellation
   - **File:** `remoting/getty/pool.go:81`
   - **Problem:** `time.Sleep` cannot be interrupted by `ctx.Done()`; 
connection acquisition may hang during shutdown.
   
   ### 17. `MethodMapper` chained type assertion panic risk
   - **File:** `common/rpc_service.go:378`
   - **Code:** `method.Func.Call(...)[0].Interface().(map[string]string)`
   - **Problem:** If `MethodMapper` returns a nil interface, the chained 
assertion can panic.
   
   ---
   
   ## 💭 P2 — Robustness / Anti-pattern
   
   ### 18. Explicit `Unlock` without `defer`
   - **File:** `registry/directory/directory.go:771-773`
   - **Problem:** `registerLock.Lock(); ...; Unlock()` — a panic between leaves 
the lock permanently held.
   - **Fix:** `defer dir.registerLock.Unlock()`.
   
   ### 19. `GetLocalIP` error ignored
   - **File:** `common/host_util.go:47`
   - **Problem:** `localIp, _ = gxnet.GetLocalIP()` — on failure `localIp` is 
empty, producing an invalid registry address silently.
   
   ### 20. Config validity errors ignored
   - **File:** `remoting/getty/config.go:143,169`
   - **Problem:** `defaultClientConfig.CheckValidity()` / 
`defaultServerConfig.CheckValidity()` results discarded.
   
   ### 21. JSON unmarshal error ignored
   - **File:** `remoting/polaris/parser/parser.go:91`
   - **Problem:** `json.Unmarshal(data, &searchVal)` error discarded.
   
   ### 22. `stopListen` error ignored
   - **File:** `config_center/nacos/listener.go:242`
   - **Problem:** Returned error from `stopListen` discarded.
   
   ### 23. `recover` without logging
   - **File:** `graceful_shutdown/shutdown.go:425`
   - **Problem:** A `recover()` swallows the panic silently, hiding the root 
cause.
   
   ### 24. Attachment type assertion (accesslog)
   - **File:** `filter/accesslog/filter.go`
   - **Problem:** Attachment values are type-asserted without `ok`; unexpected 
attachment types panic.
   
   ---
   
   ## Suggested Next Steps
   
   1. **Immediate (P0):** fix #1–#7. #1 (ZK concurrent map) is the most severe 
— it can crash the process on any concurrent config push + data change.
   2. **Enablers:** enable `errorlint`, `bodyclose`, `noctx`, `nilerr` in 
`.golangci.yml` (already discussed in `CODE_REVIEW.md`) to catch P1/P2 
systematically.
   3. **Tests:** add `-race` to `make test` to surface the goroutine-leak / 
concurrent-map issues in CI.
   
   Happy to open PRs for any of these if maintainers confirm the preferred fix 
direction.
   


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