})
}
}
+
+func TestSynctestCondSignalFromNoBubble(t *testing.T) {
+ for _, test := range []string{
+ "SynctestCond/signal/no_bubble",
+ "SynctestCond/broadcast/no_bubble",
+ "SynctestCond/signal/other_bubble",
+ "SynctestCond/broadcast/other_bubble",
+ } {
+ t.Run(test, func(t *testing.T) {
+ output := runTestProg(t, "testprog", test)
+ want := "fatal error: semaphore wake of synctest goroutine from outside bubble"
+ if !strings.Contains(output, want) {
+ t.Fatalf("output:\n%s\n\nwant output containing: %s", output, want)
+ }
+ })
+ }
+}
s.next = nil
if s.g.bubble != nil && getg().bubble != s.g.bubble {
println("semaphore wake of synctest goroutine", s.g.goid, "from outside bubble")
- panic("semaphore wake of synctest goroutine from outside bubble")
+ fatal("semaphore wake of synctest goroutine from outside bubble")
}
readyWithTime(s, 4)
s = next
s.next = nil
if s.g.bubble != nil && getg().bubble != s.g.bubble {
println("semaphore wake of synctest goroutine", s.g.goid, "from outside bubble")
- panic("semaphore wake of synctest goroutine from outside bubble")
+ fatal("semaphore wake of synctest goroutine from outside bubble")
}
readyWithTime(s, 4)
return
--- /dev/null
+// Copyright 2025 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 main
+
+import (
+ "internal/synctest"
+ "sync"
+)
+
+func init() {
+ register("SynctestCond/signal/no_bubble", func() {
+ synctestCond(func(cond *sync.Cond) {
+ cond.Signal()
+ })
+ })
+ register("SynctestCond/broadcast/no_bubble", func() {
+ synctestCond(func(cond *sync.Cond) {
+ cond.Broadcast()
+ })
+ })
+ register("SynctestCond/signal/other_bubble", func() {
+ synctestCond(func(cond *sync.Cond) {
+ synctest.Run(cond.Signal)
+ })
+ })
+ register("SynctestCond/broadcast/other_bubble", func() {
+ synctestCond(func(cond *sync.Cond) {
+ synctest.Run(cond.Broadcast)
+ })
+ })
+}
+
+func synctestCond(f func(*sync.Cond)) {
+ var (
+ mu sync.Mutex
+ cond = sync.NewCond(&mu)
+ readyc = make(chan struct{})
+ wg sync.WaitGroup
+ )
+ defer wg.Wait()
+ wg.Go(func() {
+ synctest.Run(func() {
+ go func() {
+ mu.Lock()
+ defer mu.Unlock()
+ cond.Wait()
+ }()
+ synctest.Wait()
+ <-readyc // #1: signal that cond.Wait is waiting
+ <-readyc // #2: wait to continue
+ cond.Signal()
+ })
+ })
+ readyc <- struct{}{}
+ f(cond)
+}
//
// A [sync.WaitGroup] becomes associated with a bubble on the first
// call to Add or Go. Once a WaitGroup is associated with a bubble,
-// calling Add or Go from outside that bubble panics.
+// calling Add or Go from outside that bubble is a fatal error.
+//
+// [sync.Cond.Wait] is durably blocking. Waking a goroutine in a bubble
+// blocked on Cond.Wait from outside the bubble is a fatal error.
//
// Cleanup functions and finalizers registered with
// [runtime.AddCleanup] and [runtime.SetFinalizer]