Skip to content

fix(websocket): repair race in TestUpdateAgentHeartbeat#256

Merged
JoshuaAFerguson merged 1 commit into
mainfrom
fix/heartbeat-test-race
Apr 25, 2026
Merged

fix(websocket): repair race in TestUpdateAgentHeartbeat#256
JoshuaAFerguson merged 1 commit into
mainfrom
fix/heartbeat-test-race

Conversation

@JoshuaAFerguson

Copy link
Copy Markdown
Member

Summary

`Run()` executes in a goroutine and calls `handleRegister`, which fires a sqlmock-backed `Exec` for the registration `UPDATE`. The test then calls `mock.ExpectExec` to declare the heartbeat expectation. `go-sqlmock <=1.5.2` doesn't synchronize `ExpectExec` writes against `exec` reads on its internal `expected[]` slice — the original `time.Sleep(100ms)` was a guess and not always enough, so `-race` flagged it intermittently.

```
WARNING: DATA RACE
Write at … by goroutine 23:
go-sqlmock.(*sqlmock).ExpectExec()
…websocket.TestUpdateAgentHeartbeat() agent_hub_test.go:233

Previous read at … by goroutine 25:
go-sqlmock.(*sqlmock).exec()
…websocket.(*AgentHub).handleRegister() agent_hub.go:265
…websocket.(*AgentHub).Run() agent_hub.go:216
```

Fix

Replace the sleep with a deadline poll on `mock.ExpectationsWereMet()`. The poll only completes after the registration `UPDATE` has been consumed, so the subsequent `ExpectExec` runs without a concurrent reader.

Verified

`go test -race -count=20` → 20/20 clean (was failing intermittently before; CI race had been failing for 5+ months).

Test plan

Run() executes inside a goroutine and calls handleRegister, which
issues a sqlmock-backed Exec for the registration UPDATE. The test
then calls mock.ExpectExec to declare the heartbeat expectation.

go-sqlmock <=1.5.2 does not synchronize ExpectExec writes against
exec reads on its internal expected[] slice — the original
time.Sleep(100ms) was not enough to guarantee the registration Exec
had finished, so -race intermittently flagged a write-vs-read race.

Replace the sleep with a deadline poll on mock.ExpectationsWereMet().
The poll completes only after the registration UPDATE has been
consumed, so subsequent ExpectExec runs without a concurrent reader.

Verified with `go test -race -count=20`: 20/20 clean (was failing
~50% before).
@JoshuaAFerguson JoshuaAFerguson merged commit 2e6c90a into main Apr 25, 2026
13 of 19 checks passed
@JoshuaAFerguson JoshuaAFerguson deleted the fix/heartbeat-test-race branch April 25, 2026 18:04
JoshuaAFerguson added a commit that referenced this pull request Apr 25, 2026
Two pre-existing CI failures newly visible after PR #255 / #256 made
the lint and test infrastructure functional:

1. TestProcessCommandAgentConnected raced on go-sqlmock for the same
   reason as TestUpdateAgentHeartbeat (PR #256). Apply the same
   ExpectationsWereMet polling pattern.

2. golangci-lint v2.5 (newly running) found 5 unchecked Close() return
   values in k8s-agent (vnc tunnel, websocket conn, two HTTP retry
   bodies) plus one staticcheck ST1005 capitalized error string.

Verified: 10 race reruns clean, both modules build clean.

Other test files (agent_hub_redis_test.go, websocket_enterprise_test.go,
etc) use the same time.Sleep + mock.ExpectExec pattern. Deferred to a
follow-up — they have not yet failed in CI under -race.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant