Day 068 — The Race Detector (-race)¶
Month 3 · Week 2 · ⬅ Day 067 · Day 069 ➡ · Journal index
🎯 Learning Objective¶
Catch data races with Go's -race flag, read the report it prints, and fold -race into the normal test workflow — while understanding its limits.
📚 Topics¶
go run/test/build -race, what it instruments, and the cost- Reading a race report (the two conflicting accesses + goroutine creation stacks)
- Why
-raceonly finds races that actually happen at runtime
📖 Reading / Sources¶
📝 Notes¶
- The [[race-detector]] is built into the toolchain: add
-racetogo run,go test,go build, orgo install. It compiles your program with ThreadSanitizer instrumentation that watches memory accesses at runtime. - It is a dynamic detector: it only flags a race if a racy interleaving actually occurs during this run. No report ≠ race-free; it cannot prove absence. Run it under realistic load and in tests with concurrency.
- A race report shows two stacks (the conflicting Read and Write), where each goroutine was created, and the variable involved. The fix is to add synchronisation (mutex/atomic/channel) so the two accesses are ordered.
- Cost: roughly 2–20× slower and 5–10× more memory; instrumentation is significant, so run
-racein CI/tests, not in production binaries. -racefinds data races (unsynchronised concurrent access), not logical races, deadlocks, or leaks. Deadlocks surface as a runtime "all goroutines are asleep" panic; leaks need other tooling (goroutine dumps,goleak-style checks).- Make it routine:
go test -race ./.... Concurrency bugs are non-deterministic and hide from a plaingo test; the detector is how you surface them deterministically-ish. - Pair
-racewithgo vet(catches copied locks, lostcontextcancels, badPrintfverbs) — static + dynamic together cover most concurrency footguns.
💻 Code Examples¶
$ go run -race ./examples/month-03/mutex # with the lock removed
==================
WARNING: DATA RACE
Write at 0x00c0000b4010 by goroutine 8:
main.(*SafeCounter).Inc()
.../mutex/main.go:34 +0x...
Previous write at 0x00c0000b4010 by goroutine 7:
main.(*SafeCounter).Inc()
.../mutex/main.go:34 +0x...
Goroutine 8 (running) created at:
main.main()
.../mutex/main.go:71 +0x...
==================
Found 1 data race(s)
exit status 66
Reproduce: delete the
Lock/Unlockinexamples/month-03/mutex/main.goand rungo run -race ./examples/month-03/mutex. With the lock in place the run is clean.
🏋️ Exercises / Practice¶
| Exercise | Status | Link |
|---|---|---|
Re-run all week-2 tests under -race |
✅ | go test -race ./exercises/month-03/week-2/... |
🐛 Mistakes Made¶
- Assumed a clean
go testmeant my code was race-free; adding-raceimmediately surfaced an unguardedappend. Lesson:-raceis the real check. - Tried to ship a
-racebinary "to be safe" — it was far slower and heavier.-racebelongs in tests/CI, not production.
❓ Open Questions¶
- How do I deterministically force a rare interleaving the detector keeps missing? (Stress loops,
t.Parallel,GOMAXPROCS>1, andruntime.Gosched()to widen windows.)
🧠 Active Recall (answer without looking)¶
- Q: Does a clean
go test -racerun prove your code has no data races?A
No. The detector is dynamic — it only reports races on interleavings that actually occurred this run. It can't prove absence; exercise concurrency under load to raise confidence.
2. Q: Will -race catch a deadlock or a goroutine leak? A
No. -race finds data races only. A full deadlock triggers the runtime's "all goroutines are asleep" panic; leaks need goroutine-dump/leak tooling.
🪶 Feynman Reflection¶
The race detector is a wiretap on memory: it watches who reads and writes each address and shouts when two goroutines touch the same spot without a "happens-before" handshake. It only hears the calls that actually happen, so a quiet wiretap means "none seen", not "none exist".
🕳️ Knowledge Gaps¶
- The precise happens-before edges
-racereasons about — exactly tomorrow's memory-model topic.
✅ Summary¶
I run go test -race ./... as routine, can read the two-stack race report and fix it with synchronisation, and I know -race is a dynamic detector for data races only — not a proof, and not for production builds.
⏭️ Next Steps / Prep for Tomorrow¶
- Day 069: the Go memory model — the happens-before rules that make today's synchronisation correct.
| Time spent | Difficulty | Confidence |
|---|---|---|
| 90 min | 🟦🟦⬜⬜⬜ | 🟦🟦🟦🟦⬜ |
Suggested commit: docs(journal): the race detector and -race workflow (day 068)