diff --git a/src/runtime/chan_test.go b/src/runtime/chan_test.go index 66dfd6f8d87..0b918bb99f6 100644 --- a/src/runtime/chan_test.go +++ b/src/runtime/chan_test.go @@ -218,6 +218,81 @@ func TestNonblockRecvRace(t *testing.T) { } } +// This test checks that select acts on the state of the channels at one +// moment in the execution, not over a smeared time window. +// In the test, one goroutine does: +// create c1, c2 +// make c1 ready for receiving +// create second goroutine +// make c2 ready for receiving +// make c1 no longer ready for receiving (if possible) +// The second goroutine does a non-blocking select receiving from c1 and c2. +// From the time the second goroutine is created, at least one of c1 and c2 +// is always ready for receiving, so the select in the second goroutine must +// always receive from one or the other. It must never execute the default case. +func TestNonblockSelectRace(t *testing.T) { + n := 100000 + if testing.Short() { + n = 1000 + } + done := make(chan bool, 1) + for i := 0; i < n; i++ { + c1 := make(chan int, 1) + c2 := make(chan int, 1) + c1 <- 1 + go func() { + select { + case <-c1: + case <-c2: + default: + done <- false + return + } + done <- true + }() + c2 <- 1 + select { + case <-c1: + default: + } + if !<-done { + t.Fatal("no chan is ready") + } + } +} + +// Same as TestNonblockSelectRace, but close(c2) replaces c2 <- 1. +func TestNonblockSelectRace2(t *testing.T) { + n := 100000 + if testing.Short() { + n = 1000 + } + done := make(chan bool, 1) + for i := 0; i < n; i++ { + c1 := make(chan int, 1) + c2 := make(chan int) + c1 <- 1 + go func() { + select { + case <-c1: + case <-c2: + default: + done <- false + return + } + done <- true + }() + close(c2) + select { + case <-c1: + default: + } + if !<-done { + t.Fatal("no chan is ready") + } + } +} + func TestSelfSelect(t *testing.T) { // Ensure that send/recv on the same chan in select // does not crash nor deadlock.