Many Hands, One Ledger
Real servers don't take turns.
What you're wiring up
This stage flips the accept loop to go handle(conn) — one goroutine per client — and immediately breaks the Stage 3 machine: two clients writing the same map is a data race, and Go's race detector will literally name the two lines responsible. The fix is the interlock: a sync.RWMutex so readers don't queue behind readers.
The proof that locking is about atomicity, not just crash avoidance, is INCR: read-modify-write must be one indivisible act, or two clients incrementing 1000 times each land short of 2000. This is DDIA's race conditions made personal.
Assembly steps
hint
The race detector's stack trace names the exact two lines touching the map. Reading it is the skill.
hint
defer mu.Unlock() inside small store methods keeps lock scope honest; holding a lock while writing to a slow client is how real outages happen.
hint
Get-then-Set as two locked calls loses updates — the hammer test will catch it. One method, one lock hold.
hint
Shared parser state is the sneaky second race.
Go deeper (after it passes)
Pairs with Clean Code M10 — Clean Concurrency and DDIA M7 — Transactions Without Tears.