#6412 put PeerStateByIP (one read per private-service request / DNS answer)
onto the status recorder lock and switched that lock from sync.Mutex to
sync.RWMutex. The RWMutex wins a clean microbenchmark, but on a busy client
bringing up ~1000 peers the steady data-path read flood plus a slow lock
holder (a relay handshake under GetRelayStates, a 1000-peer FullStats dump
under RefreshWireGuardStats, or an ordinary GetFullStatus poll) starves the
connect-side writes: Go's RWMutex does not protect a writer from a continuous
stream of readers, so peer state transitions stall and peers fail to connect.
Revert d.mux to sync.Mutex, whose starvation-prevention handoff keeps writes
fair. The lost read parallelism is not a bottleneck at this scale.
Add status_contention_test.go, which reproduces the scenario and includes a
regression guard (TestStatusRecorderContention) that fails if the connect-side
write path regresses toward the starving RWMutex behaviour. Real recorder, same
workload, lock type as the only variable:
lock writes(connect) write-p99 write-p999 write-max
RWMutex ~22,000 10.4ms 96ms 131ms (guard FAILS)
Mutex ~97,000 4.4ms 7.7ms 14.6ms (guard passes)