main.go:1585–1627
Two goroutines concurrently read/write the shared *bool
EOFOnWriteFromServerToClient (allocated via new(bool) by whichever finishes
first) with no synchronization. Both goroutines check == nil, then
assign the pointer and write through it; the final if *EOFOnWriteFromServerToClient
at line 1626 (after wg.Wait()) reads it from the parent goroutine.
- The
go test -race run passes only because in practice both copy loops
tend to complete-and-close in a serialized way under the test workload; it is
not guaranteed to trigger. It is a genuine data race per the Go memory model.
- It is also pre-existing (introduced in commit
051d3048, before this
branch), but since this branch's whole purpose is concurrency hardening it is
worth closing now.
Fix: drop the shared-pointer pattern. wg.Wait() already happens-before
both goroutine exits, so a single bool per goroutine written before wg.Done()
and read after wg.Wait() is race-free:
var eofServerToClient bool
go func() {
defer wg.Done()
... copy clientReader -> serviceConnection ...
eofServerToClient = true // written before wg.Done()
serviceConnection.Close()
}()
go func() {
defer wg.Done()
... copy serviceConnection -> clientConnection ...
// eofServerToClient stays false
clientConnection.Close()
}()
wg.Wait()
// reads after happens-before edge — safe
var reason string
if eofServerToClient { ... }
main.go:1585–1627Two goroutines concurrently read/write the shared
*boolEOFOnWriteFromServerToClient(allocated vianew(bool)by whichever finishesfirst) with no synchronization. Both goroutines check
== nil, thenassign the pointer and write through it; the final
if *EOFOnWriteFromServerToClientat line 1626 (after
wg.Wait()) reads it from the parent goroutine.go test -racerun passes only because in practice both copy loopstend to complete-and-close in a serialized way under the test workload; it is
not guaranteed to trigger. It is a genuine data race per the Go memory model.
051d3048, before thisbranch), but since this branch's whole purpose is concurrency hardening it is
worth closing now.
Fix: drop the shared-pointer pattern.
wg.Wait()already happens-beforeboth goroutine exits, so a single
boolper goroutine written beforewg.Done()and read after
wg.Wait()is race-free: