mirror of
https://github.com/golang/go.git
synced 2025-10-19 19:13:18 +00:00
runtime, testing/synctest: stop advancing time when main goroutine exits
Once the goroutine started by synctest.Run exits, stop advancing the fake clock in its bubble. This avoids confusing situations where a bubble remains alive indefinitely while a background goroutine reads from a time.Ticker or otherwise advances the clock. For #67434 Change-Id: Id608ffe3c7d7b07747b56a21f365787fb9a057d7 Reviewed-on: https://go-review.googlesource.com/c/go/+/662155 Reviewed-by: Michael Pratt <mpratt@google.com> LUCI-TryBot-Result: Go LUCI <golang-scoped@luci-project-accounts.iam.gserviceaccount.com> Auto-Submit: Damien Neil <dneil@google.com>
This commit is contained in:
parent
4e63ae46e0
commit
d6c8bedc7b
3 changed files with 54 additions and 9 deletions
|
@ -191,6 +191,18 @@ func TestTimeAfter(t *testing.T) {
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestTimerAfterBubbleExit(t *testing.T) {
|
||||||
|
run := false
|
||||||
|
synctest.Run(func() {
|
||||||
|
time.AfterFunc(1*time.Second, func() {
|
||||||
|
run = true
|
||||||
|
})
|
||||||
|
})
|
||||||
|
if run {
|
||||||
|
t.Errorf("timer ran before bubble exit")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
func TestTimerFromOutsideBubble(t *testing.T) {
|
func TestTimerFromOutsideBubble(t *testing.T) {
|
||||||
tm := time.NewTimer(10 * time.Millisecond)
|
tm := time.NewTimer(10 * time.Millisecond)
|
||||||
synctest.Run(func() {
|
synctest.Run(func() {
|
||||||
|
@ -308,6 +320,18 @@ func TestDeadlockChild(t *testing.T) {
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestDeadlockTicker(t *testing.T) {
|
||||||
|
defer wantPanic(t, "deadlock: all goroutines in bubble are blocked")
|
||||||
|
synctest.Run(func() {
|
||||||
|
go func() {
|
||||||
|
for range time.Tick(1 * time.Second) {
|
||||||
|
t.Errorf("ticker unexpectedly ran")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}()
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
func TestCond(t *testing.T) {
|
func TestCond(t *testing.T) {
|
||||||
synctest.Run(func() {
|
synctest.Run(func() {
|
||||||
var mu sync.Mutex
|
var mu sync.Mutex
|
||||||
|
|
|
@ -5,6 +5,7 @@
|
||||||
package runtime
|
package runtime
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"internal/runtime/sys"
|
||||||
"unsafe"
|
"unsafe"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
@ -15,7 +16,9 @@ type synctestGroup struct {
|
||||||
now int64 // current fake time
|
now int64 // current fake time
|
||||||
root *g // caller of synctest.Run
|
root *g // caller of synctest.Run
|
||||||
waiter *g // caller of synctest.Wait
|
waiter *g // caller of synctest.Wait
|
||||||
|
main *g // goroutine started by synctest.Run
|
||||||
waiting bool // true if a goroutine is calling synctest.Wait
|
waiting bool // true if a goroutine is calling synctest.Wait
|
||||||
|
done bool // true if main has exited
|
||||||
|
|
||||||
// The group is active (not blocked) so long as running > 0 || active > 0.
|
// The group is active (not blocked) so long as running > 0 || active > 0.
|
||||||
//
|
//
|
||||||
|
@ -60,6 +63,9 @@ func (sg *synctestGroup) changegstatus(gp *g, oldval, newval uint32) {
|
||||||
case _Gdead:
|
case _Gdead:
|
||||||
isRunning = false
|
isRunning = false
|
||||||
totalDelta--
|
totalDelta--
|
||||||
|
if gp == sg.main {
|
||||||
|
sg.done = true
|
||||||
|
}
|
||||||
case _Gwaiting:
|
case _Gwaiting:
|
||||||
if gp.waitreason.isIdleInSynctest() {
|
if gp.waitreason.isIdleInSynctest() {
|
||||||
isRunning = false
|
isRunning = false
|
||||||
|
@ -167,24 +173,32 @@ func synctestRun(f func()) {
|
||||||
if gp.syncGroup != nil {
|
if gp.syncGroup != nil {
|
||||||
panic("synctest.Run called from within a synctest bubble")
|
panic("synctest.Run called from within a synctest bubble")
|
||||||
}
|
}
|
||||||
gp.syncGroup = &synctestGroup{
|
sg := &synctestGroup{
|
||||||
total: 1,
|
total: 1,
|
||||||
running: 1,
|
running: 1,
|
||||||
root: gp,
|
root: gp,
|
||||||
}
|
}
|
||||||
const synctestBaseTime = 946684800000000000 // midnight UTC 2000-01-01
|
const synctestBaseTime = 946684800000000000 // midnight UTC 2000-01-01
|
||||||
gp.syncGroup.now = synctestBaseTime
|
sg.now = synctestBaseTime
|
||||||
gp.syncGroup.timers.syncGroup = gp.syncGroup
|
sg.timers.syncGroup = sg
|
||||||
lockInit(&gp.syncGroup.mu, lockRankSynctest)
|
lockInit(&sg.mu, lockRankSynctest)
|
||||||
lockInit(&gp.syncGroup.timers.mu, lockRankTimers)
|
lockInit(&sg.timers.mu, lockRankTimers)
|
||||||
|
|
||||||
|
gp.syncGroup = sg
|
||||||
defer func() {
|
defer func() {
|
||||||
gp.syncGroup = nil
|
gp.syncGroup = nil
|
||||||
}()
|
}()
|
||||||
|
|
||||||
fv := *(**funcval)(unsafe.Pointer(&f))
|
// This is newproc, but also records the new g in sg.main.
|
||||||
newproc(fv)
|
pc := sys.GetCallerPC()
|
||||||
|
systemstack(func() {
|
||||||
|
fv := *(**funcval)(unsafe.Pointer(&f))
|
||||||
|
sg.main = newproc1(fv, gp, pc, false, waitReasonZero)
|
||||||
|
pp := getg().m.p.ptr()
|
||||||
|
runqput(pp, sg.main, true)
|
||||||
|
wakep()
|
||||||
|
})
|
||||||
|
|
||||||
sg := gp.syncGroup
|
|
||||||
lock(&sg.mu)
|
lock(&sg.mu)
|
||||||
sg.active++
|
sg.active++
|
||||||
for {
|
for {
|
||||||
|
@ -209,6 +223,10 @@ func synctestRun(f func()) {
|
||||||
if next < sg.now {
|
if next < sg.now {
|
||||||
throw("time went backwards")
|
throw("time went backwards")
|
||||||
}
|
}
|
||||||
|
if sg.done {
|
||||||
|
// Time stops once the bubble's main goroutine has exited.
|
||||||
|
break
|
||||||
|
}
|
||||||
sg.now = next
|
sg.now = next
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -28,7 +28,10 @@ import (
|
||||||
// goroutines are blocked and return after the bubble's clock has
|
// goroutines are blocked and return after the bubble's clock has
|
||||||
// advanced. See [Wait] for the specific definition of blocked.
|
// advanced. See [Wait] for the specific definition of blocked.
|
||||||
//
|
//
|
||||||
// If every goroutine is blocked and there are no timers scheduled,
|
// Time stops advancing when f returns.
|
||||||
|
//
|
||||||
|
// If every goroutine is blocked and either
|
||||||
|
// no timers are scheduled or f has returned,
|
||||||
// Run panics.
|
// Run panics.
|
||||||
//
|
//
|
||||||
// Channels, time.Timers, and time.Tickers created within the bubble
|
// Channels, time.Timers, and time.Tickers created within the bubble
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue