diff --git a/src/context/example_test.go b/src/context/example_test.go index 38549a12de898e..03333b5ccae959 100644 --- a/src/context/example_test.go +++ b/src/context/example_test.go @@ -125,25 +125,64 @@ func ExampleWithValue() { // This example uses AfterFunc to define a function which waits on a sync.Cond, // stopping the wait when a context is canceled. func ExampleAfterFunc_cond() { - waitOnCond := func(ctx context.Context, cond *sync.Cond) error { - stopf := context.AfterFunc(ctx, cond.Broadcast) + waitOnCond := func(ctx context.Context, cond *sync.Cond, conditionMet func() bool) error { + stopf := context.AfterFunc(ctx, func() { + // We need to acquire cond.L here to be sure that the Broadcast + // below won't occur before the call to Wait, which would result + // in a missed signal (and deadlock). + cond.L.Lock() + defer cond.L.Unlock() + + // If multiple goroutines are waiting on cond simultaneously, + // we need to make sure we wake up exactly this one. + // That means that we need to Broadcast to all of the goroutines, + // which will wake them all up. + // + // If there are N concurrent calls to waitOnCond, each of the goroutines + // will spuriously wake up O(N) other goroutines that aren't ready yet, + // so this will cause the overall CPU cost to be O(N²). + cond.Broadcast() + }) defer stopf() - cond.Wait() - return ctx.Err() + + // Since the wakeups are using Broadcast instead of Signal, this call to + // Wait may unblock due to some other goroutine's context becoming done, + // so to be sure that ctx is actually done we need to check it in a loop. + for !conditionMet() { + cond.Wait() + if ctx.Err() != nil { + return ctx.Err() + } + } + + return nil } - ctx, cancel := context.WithTimeout(context.Background(), 1*time.Millisecond) - defer cancel() + cond := sync.NewCond(new(sync.Mutex)) + + var wg sync.WaitGroup + for i := 0; i < 4; i++ { + wg.Add(1) + go func() { + defer wg.Done() - var mu sync.Mutex - cond := sync.NewCond(&mu) + ctx, cancel := context.WithTimeout(context.Background(), 1*time.Millisecond) + defer cancel() - mu.Lock() - err := waitOnCond(ctx, cond) - fmt.Println(err) + cond.L.Lock() + defer cond.L.Unlock() + + err := waitOnCond(ctx, cond, func() bool { return false }) + fmt.Println(err) + }() + } + wg.Wait() // Output: // context deadline exceeded + // context deadline exceeded + // context deadline exceeded + // context deadline exceeded } // This example uses AfterFunc to define a function which reads from a net.Conn,