go: runtime: close of a channel with pending sends incorrectly flagged by race detector
What version of Go are you using (go version)?
$ go version go version go1.11.4 windows/amd64
Does this issue reproduce with the latest release?
Yes.
What operating system and processor architecture are you using (go env)?
go env Output
$ go env set GOARCH=amd64 set GOBIN= set GOCACHE=C:\Users\kjk\AppData\Local\go-build set GOEXE=.exe set GOFLAGS= set GOHOSTARCH=amd64 set GOHOSTOS=windows set GOOS=windows set GOPATH=C:\Users\kjk\go set GOPROXY= set GORACE= set GOROOT=C:\Go set GOTMPDIR= set GOTOOLDIR=C:\Go\pkg\tool\windows_amd64 set GCCGO=gccgo set CC=gcc set CXX=g++ set CGO_ENABLED=1 set GOMOD= set CGO_CFLAGS=-g -O2 set CGO_CPPFLAGS= set CGO_CXXFLAGS=-g -O2 set CGO_FFLAGS=-g -O2 set CGO_LDFLAGS=-g -O2 set PKG_CONFIG=pkg-config set GOGCCFLAGS=-m64 -mthreads -fno-caret-diagnostics -Qunused-arguments -fmessage-length=0 -fdebug-prefix-map=C:\Users\kjk\AppData\Local\Temp\go-build206244702=/tmp/go-build -gno-record-gcc-switches
What did you do?
Save this to e.g. t.go:
package main
import (
"fmt"
"time"
)
func main() {
ch := make(chan string)
for i := 0; i < 256; i++ {
go func(n int) {
ch <- fmt.Sprintf("string %d", n)
}(i)
}
time.Sleep(time.Second)
close(ch)
}
and run go -race t.go.
What did you see instead?
I see a data race reported:
> go run -race t.go
==================
WARNING: DATA RACE
Write at 0x00c00001a130 by main goroutine:
runtime.closechan()
C:/Go/src/runtime/chan.go:327 +0x0
main.main()
C:/Users/kjk/src/t.go:16 +0xb5
Previous read at 0x00c00001a130 by goroutine 231:
runtime.chansend()
C:/Go/src/runtime/chan.go:140 +0x0
main.main.func1()
C:/Users/kjk/src/t.go:12 +0xcc
Goroutine 231 (running) created at:
main.main()
C:/Users/kjk/src/t.go:11 +0x83
==================
panic: send on closed channel
goroutine 201 [running]:
main.main.func1(0xc00001a120, 0xc3)
C:/Users/kjk/src/t.go:12 +0xcd
created by main.main
C:/Users/kjk/src/t.go:11 +0x84
panic: send on closed channel
goroutine 235 [running]:
main.main.func1(0xc00001a120, 0xe5)
C:/Users/kjk/src/t.go:12 +0xcd
created by main.main
C:/Users/kjk/src/t.go:11 +0x84
exit status 2
What did you expect to see?
I didn’t expect data race between close(ch) and pending ch <- v send.
The spec (https://golang.org/ref/spec#Channel_types) is curiously mum on the subject.
It says:
A single channel may be used in send statements, receive operations, and calls to the built-in functions cap and len by any number of goroutines without further synchronization.
By not mentioning close it implies that one has to synchronize close and sends.
But I wouldn’t know how to synchronize those. My sends have already been dispatched and closing a channel seems like a valid (and only) way to ensure those sending goroutines don’t stick forever (assumingI recover the resulting panic).
The data race seems harmless, as I’m done with this channel, but I run my tests with -race detection enabled and this makes the test fail.
About this issue
- Original URL
- State: closed
- Created 5 years ago
- Comments: 23 (12 by maintainers)
(I assume you mean send and close, not send and receive?)
The runtime arranges to have
closechanwrite a location thatchansendreads on purpose. This isn’t an accidental race detection. It’s not a bug, it’s intentional. There are even tests:runtime/race/testdata/chan_test.go:TestRaceChanSyncCloseSendandTestRaceChanAsyncCloseSend.The problem is that for almost everyone else, it is a bug. It is a known gotcha for inexperienced Go programmers. Their program works fine while testing, but then in production the scheduler starts doing something differently and their program panics. We’re very on board with the fact that the race detector helps us detect this issue more reliably in testing.
If they are blocked on channel reads, then you can use a
selecton a cancellation channel to get them out of that read. If they are blocked on anio.Reader, then it is trickier. It would be nice to fix #20280 soio.Readeroperations are cancellable.You’re right that the spec is somewhat muddied in where exactly a race should be reported, and where it shouldn’t. But we definitely want a race between send and close. Not because of the spec, but because reporting that race helps real users fix real bugs.
The usual approach would be a synchronous callback function or explicit iterator. Channels in exported APIs are rarely the most elegant solution.
(For more detail, see the first section of Rethinking Classical Concurrency Patterns.)
The only reason to close a channel is to tell the receiver that there is nothing left to receive. Therefore, closing a channel should always be thought of a send operation on a channel, and only the sender should close a channel. When you have multiple senders, they must coordinate when to close the channel.
Note also that sending on a closed channel causes a run-time panic. The most obvious way to see that your original code has a race is to remove the call to
time.Sleep. That program will panic, because it has a race condition. You can never remove a race condition by adding atime.Sleepcall.Yes.
Wait for the senders to complete. (For your original example, that might look something like https://play.golang.org/p/4LtjIUA0tla.)
Correct, that pattern is invalid. You can terminate communication by closing a channel on which the goroutines are receiving, but not one on which they are sending.
Note that in your original example, the goroutines are not terminated: if any are remaining, they either leak or panic. (See https://play.golang.org/p/2Tjo2a3Q3iN and https://play.golang.org/p/sHi6fxSgEwv respectively.)
It’s a valid transaction race. There is no guarantee the sends complete before the close, which would result in a panic. The race detector actually helped you here.