Files
zhenshan.caoandClaude Opus 5 955072942c test: fix data race on the mini cluster node maps during teardown (#53534)
`clearRedundantNodes` passes `lo.MapToSlice(c.mixcoord)`,
`lo.MapToSlice(c.querynode)` and the three others as arguments to `go
clearNodes(...)`. Arguments are evaluated in the calling goroutine, and
`clearNodes` takes `c.mu` only inside its own body, so those five map
reads happen with no lock held.

A Milvus process that exits on its own runs the stop callback installed
in `getOptions`, which calls `clearProcess` and deletes from the same
maps while holding `c.mu`. A node stopping while
`MiniClusterSuite.TearDownTest` runs `Reset` is therefore an
unsynchronized map read against a concurrent delete:

```
cluster.go:391  clearRedundantNodes -> lo.MapToSlice(c.querynode)   read, no lock
cluster.go:739  clearProcess        -> delete(c.querynode, nodeID)  write, holds c.mu
```

### Fix

Take the snapshot of all five maps under `c.mu` before starting the
goroutines. `clearNodes` still takes the lock itself, so the snapshot is
released before any of them run and the locking discipline of the rest
of the function is unchanged.

Reading a node that `clearProcess` removes between the snapshot and the
loop is harmless: `clearNodes` only calls `IsWorking`, `GetNodeID` and
`Stop` on the pointer, which is what the code already did.

### Verification

Reproduced with the race detector on an integration suite that adds a
QueryNode and lets it stop before teardown. The race fires 2 out of 2
runs before this change and 0 out of 1 after, with the case passing.

Twelve integration test files call `AddQueryNode`, so any of them can
hit the same window; whether a given suite hits it depends on whether a
node happens to be stopping when `Reset` runs.

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Signed-off-by: zhenshan.cao <[email protected]>
Co-authored-by: Claude Opus 5 (1M context) <[email protected]>
2026-09-17 04:43:35 +08:00
..