From d6c8bedc7b3d2de7714dca75bd05912b371538f1 Mon Sep 17 00:00:00 2001 From: Damien Neil Date: Tue, 1 Apr 2025 15:43:22 -0700 Subject: 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 LUCI-TryBot-Result: Go LUCI Auto-Submit: Damien Neil --- src/runtime/synctest.go | 34 ++++++++++++++++++++++++++-------- 1 file changed, 26 insertions(+), 8 deletions(-) (limited to 'src/runtime') diff --git a/src/runtime/synctest.go b/src/runtime/synctest.go index b197758ad9..36d6fa67c7 100644 --- a/src/runtime/synctest.go +++ b/src/runtime/synctest.go @@ -5,6 +5,7 @@ package runtime import ( + "internal/runtime/sys" "unsafe" ) @@ -15,7 +16,9 @@ type synctestGroup struct { now int64 // current fake time root *g // caller of synctest.Run waiter *g // caller of synctest.Wait + main *g // goroutine started by synctest.Run 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. // @@ -60,6 +63,9 @@ func (sg *synctestGroup) changegstatus(gp *g, oldval, newval uint32) { case _Gdead: isRunning = false totalDelta-- + if gp == sg.main { + sg.done = true + } case _Gwaiting: if gp.waitreason.isIdleInSynctest() { isRunning = false @@ -167,24 +173,32 @@ func synctestRun(f func()) { if gp.syncGroup != nil { panic("synctest.Run called from within a synctest bubble") } - gp.syncGroup = &synctestGroup{ + sg := &synctestGroup{ total: 1, running: 1, root: gp, } const synctestBaseTime = 946684800000000000 // midnight UTC 2000-01-01 - gp.syncGroup.now = synctestBaseTime - gp.syncGroup.timers.syncGroup = gp.syncGroup - lockInit(&gp.syncGroup.mu, lockRankSynctest) - lockInit(&gp.syncGroup.timers.mu, lockRankTimers) + sg.now = synctestBaseTime + sg.timers.syncGroup = sg + lockInit(&sg.mu, lockRankSynctest) + lockInit(&sg.timers.mu, lockRankTimers) + + gp.syncGroup = sg defer func() { gp.syncGroup = nil }() - fv := *(**funcval)(unsafe.Pointer(&f)) - newproc(fv) + // This is newproc, but also records the new g in sg.main. + 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) sg.active++ for { @@ -209,6 +223,10 @@ func synctestRun(f func()) { if next < sg.now { throw("time went backwards") } + if sg.done { + // Time stops once the bubble's main goroutine has exited. + break + } sg.now = next } -- cgit v1.3-5-g9baa