package cond import ( "sync" "testing" "time" ) func TestCondSignal(t *testing.T) { var m sync.Mutex c := New(&m) n := 2 running := make(chan bool, n) awake := make(chan bool, n) for i := 0; i < n; i++ { go func() { m.Lock() running <- true c.Wait() awake <- true m.Unlock() }() } for i := 0; i < n; i++ { <-running // Wait for everyone to run. } for n > 0 { select { case <-awake: t.Fatal("goroutine not asleep") default: } m.Lock() c.Signal() m.Unlock() <-awake // Will deadlock if no goroutine wakes up select { case <-awake: t.Fatal("too many goroutines awake") default: } n-- } c.Signal() } func TestCondSignalGenerations(t *testing.T) { var m sync.Mutex c := New(&m) n := 100 running := make(chan bool, n) awake := make(chan int, n) for i := 0; i < n; i++ { go func(i int) { m.Lock() running <- true c.Wait() awake <- i m.Unlock() }(i) if i > 0 { a := <-awake if a != i-1 { t.Fatalf("wrong goroutine woke up: want %d, got %d", i-1, a) } } <-running m.Lock() c.Signal() m.Unlock() } } // nolint func TestCondBroadcast(t *testing.T) { var m sync.Mutex c := New(&m) n := 200 running := make(chan int, n) awake := make(chan int, n) exit := false for i := 0; i < n; i++ { go func(g int) { m.Lock() for !exit { running <- g c.Wait() awake <- g } m.Unlock() }(i) } for i := 0; i < n; i++ { for i := 0; i < n; i++ { <-running // Will deadlock unless n are running. } if i == n-1 { m.Lock() exit = true m.Unlock() } select { case <-awake: t.Fatal("goroutine not asleep") default: } m.Lock() c.Broadcast() m.Unlock() seen := make([]bool, n) for i := 0; i < n; i++ { g := <-awake if seen[g] { t.Fatal("goroutine woke up twice") } seen[g] = true } } select { case <-running: t.Fatal("goroutine did not exit") default: } c.Broadcast() } // nolint func TestCondSignalStealing(t *testing.T) { for iters := 0; iters < 1000; iters++ { var m sync.Mutex cond := New(&m) // Start a waiter. ch := make(chan struct{}) go func() { m.Lock() ch <- struct{}{} cond.Wait() m.Unlock() ch <- struct{}{} }() <-ch m.Lock() m.Unlock() // We know that the waiter is in the cond.Wait() call because we // synchronized with it, then acquired/released the mutex it was // holding when we synchronized. // // Start two goroutines that will race: one will broadcast on // the cond var, the other will wait on it. // // The new waiter may or may not get notified, but the first one // has to be notified. done := false go func() { cond.Broadcast() }() go func() { m.Lock() for !done { cond.Wait() } m.Unlock() }() // Check that the first waiter does get signaled. select { case <-ch: case <-time.After(2 * time.Second): t.Fatalf("First waiter didn't get broadcast.") } // Release the second waiter in case it didn't get the // broadcast. m.Lock() done = true m.Unlock() cond.Broadcast() } }