Consider the following test:
func TestDataRace(t *testing.T) {
ch := NewUnboundedChan(1)
stop := make(chan bool)
for i := 0; i < 100; i++ { // may tweak the number of iterations
go func() {
for {
select {
case <-stop:
return
default:
ch.In <- 42
<-ch.Out
}
}
}()
}
for i := 0; i < 10000; i++ { // may tweak the number of iterations
ch.Len()
}
close(stop)
}
The above test results in the following data race:
$ go test -run=DataRace -race
==================
WARNING: DATA RACE
Read at 0x00c0001309a8 by goroutine 7:
github.com/smallnest/chanx.(*RingBuffer).Len()
/Users/changkun/dev/changkun.de/chanx/ringbuffer.go:110 +0x118
github.com/smallnest/chanx.UnboundedChan.Len()
/Users/changkun/dev/changkun.de/chanx/unbounded_chan.go:18 +0xf4
github.com/smallnest/chanx.TestChanDataRace()
/Users/changkun/dev/changkun.de/chanx/unbounded_chan_test.go:191 +0xf0
testing.tRunner()
/Users/changkun/goes/go/src/testing/testing.go:1242 +0x198
Previous write at 0x00c0001309a8 by goroutine 8:
github.com/smallnest/chanx.(*RingBuffer).Read()
/Users/changkun/dev/changkun.de/chanx/ringbuffer.go:42 +0x12c
github.com/smallnest/chanx.(*RingBuffer).Pop()
/Users/changkun/dev/changkun.de/chanx/ringbuffer.go:51 +0x6c
github.com/smallnest/chanx.process()
/Users/changkun/dev/changkun.de/chanx/unbounded_chan.go:72 +0x460
Goroutine 7 (running) created at:
testing.(*T).Run()
/Users/changkun/goes/go/src/testing/testing.go:1289 +0x5b8
testing.runTests.func1()
/Users/changkun/goes/go/src/testing/testing.go:1581 +0xac
testing.tRunner()
/Users/changkun/goes/go/src/testing/testing.go:1242 +0x198
testing.runTests()
/Users/changkun/goes/go/src/testing/testing.go:1579 +0x780
testing.(*M).Run()
/Users/changkun/goes/go/src/testing/testing.go:1487 +0x928
main.main()
_testmain.go:55 +0x288
Goroutine 8 (running) created at:
github.com/smallnest/chanx.NewUnboundedChanSize()
/Users/changkun/dev/changkun.de/chanx/unbounded_chan.go:40 +0x1b0
github.com/smallnest/chanx.NewUnboundedChan()
/Users/changkun/dev/changkun.de/chanx/unbounded_chan.go:31 +0x3c
github.com/smallnest/chanx.TestChanDataRace()
/Users/changkun/dev/changkun.de/chanx/unbounded_chan_test.go:174 +0x28
testing.tRunner()
/Users/changkun/goes/go/src/testing/testing.go:1242 +0x198
==================
==================
WARNING: DATA RACE
Read at 0x00c0001309b0 by goroutine 7:
github.com/smallnest/chanx.(*RingBuffer).Len()
/Users/changkun/dev/changkun.de/chanx/ringbuffer.go:110 +0x134
github.com/smallnest/chanx.UnboundedChan.Len()
/Users/changkun/dev/changkun.de/chanx/unbounded_chan.go:18 +0xf4
github.com/smallnest/chanx.TestChanDataRace()
/Users/changkun/dev/changkun.de/chanx/unbounded_chan_test.go:191 +0xf0
testing.tRunner()
/Users/changkun/goes/go/src/testing/testing.go:1242 +0x198
Previous write at 0x00c0001309b0 by goroutine 8:
github.com/smallnest/chanx.(*RingBuffer).Write()
/Users/changkun/dev/changkun.de/chanx/ringbuffer.go:70 +0xec
github.com/smallnest/chanx.process()
/Users/changkun/dev/changkun.de/chanx/unbounded_chan.go:69 +0x44c
Goroutine 7 (running) created at:
testing.(*T).Run()
/Users/changkun/goes/go/src/testing/testing.go:1289 +0x5b8
testing.runTests.func1()
/Users/changkun/goes/go/src/testing/testing.go:1581 +0xac
testing.tRunner()
/Users/changkun/goes/go/src/testing/testing.go:1242 +0x198
testing.runTests()
/Users/changkun/goes/go/src/testing/testing.go:1579 +0x780
testing.(*M).Run()
/Users/changkun/goes/go/src/testing/testing.go:1487 +0x928
main.main()
_testmain.go:55 +0x288
Goroutine 8 (running) created at:
github.com/smallnest/chanx.NewUnboundedChanSize()
/Users/changkun/dev/changkun.de/chanx/unbounded_chan.go:40 +0x1b0
github.com/smallnest/chanx.NewUnboundedChan()
/Users/changkun/dev/changkun.de/chanx/unbounded_chan.go:31 +0x3c
github.com/smallnest/chanx.TestChanDataRace()
/Users/changkun/dev/changkun.de/chanx/unbounded_chan_test.go:174 +0x28
testing.tRunner()
/Users/changkun/goes/go/src/testing/testing.go:1242 +0x198
==================
==================
WARNING: DATA RACE
Read at 0x00c0001309a0 by goroutine 7:
github.com/smallnest/chanx.(*RingBuffer).Len()
/Users/changkun/dev/changkun.de/chanx/ringbuffer.go:118 +0x1ac
github.com/smallnest/chanx.UnboundedChan.Len()
/Users/changkun/dev/changkun.de/chanx/unbounded_chan.go:18 +0xf4
github.com/smallnest/chanx.TestChanDataRace()
/Users/changkun/dev/changkun.de/chanx/unbounded_chan_test.go:191 +0xf0
testing.tRunner()
/Users/changkun/goes/go/src/testing/testing.go:1242 +0x198
Previous write at 0x00c0001309a0 by goroutine 8:
github.com/smallnest/chanx.(*RingBuffer).grow()
/Users/changkun/dev/changkun.de/chanx/ringbuffer.go:96 +0x3bc
github.com/smallnest/chanx.(*RingBuffer).Write()
/Users/changkun/dev/changkun.de/chanx/ringbuffer.go:77 +0x18c
github.com/smallnest/chanx.process()
/Users/changkun/dev/changkun.de/chanx/unbounded_chan.go:69 +0x44c
Goroutine 7 (running) created at:
testing.(*T).Run()
/Users/changkun/goes/go/src/testing/testing.go:1289 +0x5b8
testing.runTests.func1()
/Users/changkun/goes/go/src/testing/testing.go:1581 +0xac
testing.tRunner()
/Users/changkun/goes/go/src/testing/testing.go:1242 +0x198
testing.runTests()
/Users/changkun/goes/go/src/testing/testing.go:1579 +0x780
testing.(*M).Run()
/Users/changkun/goes/go/src/testing/testing.go:1487 +0x928
main.main()
_testmain.go:55 +0x288
Goroutine 8 (running) created at:
github.com/smallnest/chanx.NewUnboundedChanSize()
/Users/changkun/dev/changkun.de/chanx/unbounded_chan.go:40 +0x1b0
github.com/smallnest/chanx.NewUnboundedChan()
/Users/changkun/dev/changkun.de/chanx/unbounded_chan.go:31 +0x3c
github.com/smallnest/chanx.TestChanDataRace()
/Users/changkun/dev/changkun.de/chanx/unbounded_chan_test.go:174 +0x28
testing.tRunner()
/Users/changkun/goes/go/src/testing/testing.go:1242 +0x198
==================
--- FAIL: TestChanDataRace (0.01s)
testing.go:1135: race detected during execution of test
FAIL
exit status 1
FAIL github.com/smallnest/chanx 0.194s
I think providing a Len()
method in abstracting an unbounded channel needs more careful handling (Mutex or lock-free pattern via an external atomic counter similar to the runtime channel implementation).
The self-created internal buffer for an unbounded channel is maintained in a separate goroutine, a read via len()
creates a race condition with any writes with respect to that buffer.
bug