mirror of https://github.com/golang/go.git
runtime, testing/synctest: breaking bubble isolation with Cond is fatal
sync.Cond.Wait is durably blocking. Waking a goroutine out of Cond.Wait from outside its bubble panics. Make this panic a fatal panic, since it leaves the notifyList in an inconsistent state. We could do some work to make this a recoverable panic, but the complexity doesn't seem worth the outcome. For #67434 Change-Id: I88874c1519c2e5c0063175297a9b120cedabcd07 Reviewed-on: https://go-review.googlesource.com/c/go/+/675617 LUCI-TryBot-Result: Go LUCI <golang-scoped@luci-project-accounts.iam.gserviceaccount.com> Reviewed-by: Michael Pratt <mpratt@google.com> Auto-Submit: Damien Neil <dneil@google.com>
This commit is contained in:
parent
555d425d17
commit
21b7e60c6b
|
|
@ -1228,3 +1228,20 @@ func TestFinalizerOrCleanupDeadlock(t *testing.T) {
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
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)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
|
||||||
|
|
@ -635,7 +635,7 @@ func notifyListNotifyAll(l *notifyList) {
|
||||||
s.next = nil
|
s.next = nil
|
||||||
if s.g.bubble != nil && getg().bubble != s.g.bubble {
|
if s.g.bubble != nil && getg().bubble != s.g.bubble {
|
||||||
println("semaphore wake of synctest goroutine", s.g.goid, "from outside 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)
|
readyWithTime(s, 4)
|
||||||
s = next
|
s = next
|
||||||
|
|
@ -692,7 +692,7 @@ func notifyListNotifyOne(l *notifyList) {
|
||||||
s.next = nil
|
s.next = nil
|
||||||
if s.g.bubble != nil && getg().bubble != s.g.bubble {
|
if s.g.bubble != nil && getg().bubble != s.g.bubble {
|
||||||
println("semaphore wake of synctest goroutine", s.g.goid, "from outside 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)
|
readyWithTime(s, 4)
|
||||||
return
|
return
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,58 @@
|
||||||
|
// 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)
|
||||||
|
}
|
||||||
|
|
@ -92,7 +92,10 @@
|
||||||
//
|
//
|
||||||
// A [sync.WaitGroup] becomes associated with a bubble on the first
|
// A [sync.WaitGroup] becomes associated with a bubble on the first
|
||||||
// call to Add or Go. Once a WaitGroup is associated with a bubble,
|
// 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
|
// Cleanup functions and finalizers registered with
|
||||||
// [runtime.AddCleanup] and [runtime.SetFinalizer]
|
// [runtime.AddCleanup] and [runtime.SetFinalizer]
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue