// Copyright 2011 The Go Authors. All rights reserved. // Use of this source code is governed by a BSD-style // license that can be found in the LICENSE file. package sync_test import ( "reflect" "runtime" . "sync" "testing" ) func TestCondSignal(t *testing.T) { var m Mutex c := NewCond(&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 Mutex c := NewCond(&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() } } func TestCondBroadcast(t *testing.T) { var m Mutex c := NewCond(&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() } func TestRace(t *testing.T) { x := 0 c := NewCond(&Mutex{}) done := make(chan bool) go func() { c.L.Lock() x = 1 c.Wait() if x != 2 { t.Error("want 2") } x = 3 c.Signal() c.L.Unlock() done <- true }() go func() { c.L.Lock() for { if x == 1 { x = 2 c.Signal() break } c.L.Unlock() runtime.Gosched() c.L.Lock() } c.L.Unlock() done <- true }() go func() { c.L.Lock() for { if x == 2 { c.Wait() if x != 3 { t.Error("want 3") } break } if x == 3 { break } c.L.Unlock() runtime.Gosched() c.L.Lock() } c.L.Unlock() done <- true }() <-done <-done <-done } func TestCondSignalStealing(t *testing.T) { for iters := 0; iters < 1000; iters++ { var m Mutex cond := NewCond(&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. <-ch // Release the second waiter in case it didn't get the // broadcast. m.Lock() done = true m.Unlock() cond.Broadcast() } } func TestCondCopy(t *testing.T) { defer func() { err := recover() if err == nil || err.(string) != "sync.Cond is copied" { t.Fatalf("got %v, expect sync.Cond is copied", err) } }() c := Cond{L: &Mutex{}} c.Signal() var c2 Cond reflect.ValueOf(&c2).Elem().Set(reflect.ValueOf(&c).Elem()) // c2 := c, hidden from vet c2.Signal() } func BenchmarkCond1(b *testing.B) { benchmarkCond(b, 1) } func BenchmarkCond2(b *testing.B) { benchmarkCond(b, 2) } func BenchmarkCond4(b *testing.B) { benchmarkCond(b, 4) } func BenchmarkCond8(b *testing.B) { benchmarkCond(b, 8) } func BenchmarkCond16(b *testing.B) { benchmarkCond(b, 16) } func BenchmarkCond32(b *testing.B) { benchmarkCond(b, 32) } func benchmarkCond(b *testing.B, waiters int) { c := NewCond(&Mutex{}) done := make(chan bool) id := 0 for routine := 0; routine < waiters+1; routine++ { go func() { for i := 0; i < b.N; i++ { c.L.Lock() if id == -1 { c.L.Unlock() break } id++ if id == waiters+1 { id = 0 c.Broadcast() } else { c.Wait() } c.L.Unlock() } c.L.Lock() id = -1 c.Broadcast() c.L.Unlock() done <- true }() } for routine := 0; routine < waiters+1; routine++ { <-done } }