Aias00 opened a new pull request, #3521:
URL: https://github.com/apache/dubbo-go/pull/3521
## What
`List` and `IsAvailable` read `dir.invokers` without a lock, while `Destroy`
reassigns it under `base.Directory.mutex` (via `DoDestroy`). The unlocked reads
race the write (data race on the slice header).
## Why
```go
// cluster/directory/static/directory.go
func (dir *directory) IsAvailable() bool {
...
if len(dir.invokers) == 0 { ... } // unlocked read
for _, invoker := range dir.invokers { ... } // unlocked range
}
func (dir *directory) List(...) []protocolbase.Invoker {
l := len(dir.invokers); ... copy(invokers, dir.invokers) // unlocked
read
}
func (dir *directory) Destroy() {
dir.DoDestroy(func() { ...; dir.invokers = []protocolbase.Invoker{} })
// write under base.Directory.mutex
}
```
`base.Directory.mutex` is unexported and not accessible from the `static`
package, so the static directory needs its own lock for `invokers`.
## Fix
Add a `sync.RWMutex` to the static `directory`:
- `IsAvailable`/`List` snapshot `invokers` under `RLock` and iterate the
snapshot.
- `List` releases the lock **before** calling `RouterChain()` (which takes
`base.Directory.mutex`) so the two locks never nest — no deadlock.
- `Destroy`'s closure writes `invokers` under the new lock (inside
`DoDestroy`, which holds `base.Directory.mutex`; no other path holds the new
lock while waiting for `base.Directory.mutex`).
## Tests
Added `TestStaticDirListVsDestroyRace`: 50 rounds of concurrent
`List`/`IsAvailable`/`Destroy`, passing under `-race`.
Fixes #3520
--
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]