mirror of
https://github.com/gesellix/Bose-SoundTouch.git
synced 2026-08-19 09:06:14 +00:00
The Devices map on WebApp was written from the startup goroutine, the /api/discover POST handler, and addDevice, while being read from every HTTP handler and the WebSocket periodic-update loop — all without any mutex. The Go runtime panics with "fatal error: concurrent map writes" or "concurrent map read and map write" on any actual collision, so this was a latent crash, not a tearing issue. Hide the map behind a sync.RWMutex and a small API: GetDevice(id) (*DeviceConnection, bool) DeviceSnapshot() []DeviceEntry DeviceCount() int AddDevice(id, conn) bool // atomic insert-or-touch TouchDevice(id) bool // fast-path LastSeen bump Update every caller — handlers, websocket, main, tests — to go through the API. addDevice's existing-host fast path uses TouchDevice; the final insert uses AddDevice so a race with another writer is rejected cleanly instead of silently overwriting. Add a TestRegistryConcurrent stress test that runs 64 goroutines doing 12,800 operations across writers, touchers, and two reader patterns. It exists to give `-race` (already on in CI) a concrete shape to catch if the encapsulation ever leaks back out. Struct-field races on conn.Status.* are not addressed by this change; they need their own follow-up. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>