test: drain healthcheck goroutine + lock read in HealthCallback test

CI on Linux flagged a race in TestStartWorker_HealthCallback_StatusChange:
the test read ui.updates while the healthchecker's per-port goroutine
(spawned by Register at checker.go:164) was still running and could
fire UpdateStatus through notifyStatusChange.

The earlier mutex on MockStatusUpdater protected the writes; the read
side was unprotected, and the goroutine had not finished by the time
the test started ranging over the slice on slower runners.

Fix:
  - call healthChecker.Unregister(fwd.ID()) to drain the per-port
    goroutine before reading
  - hold ui.mu around the slice read for belt-and-suspenders happens-
    before, regardless of goroutine timing

Verified locally with go test -race -count=20 on the targeted test
and -count=3 on the full forward package.
This commit is contained in:
2026-05-06 18:24:38 +01:00
parent 62483f9475
commit 3f11219dc1
+9 -1
View File
@@ -721,7 +721,15 @@ func TestStartWorker_HealthCallback_StatusChange(t *testing.T) {
// but MarkConnected spawns a goroutine; MarkReconnecting calls markStatus directly).
time.Sleep(20 * time.Millisecond)
// The callback should have updated status.
// Stop the healthchecker so its background per-port goroutine drains
// before we read the mock — establishes happens-before for the read and
// keeps the race detector quiet on slower CI runners.
m.healthChecker.Unregister(fwd.ID())
// The callback should have updated status. Hold the mock's lock during
// the read because background goroutines may still be unwinding.
ui.mu.Lock()
defer ui.mu.Unlock()
var sawUpdate bool
for _, u := range ui.updates {
if u.ID == fwd.ID() {