diff options
| author | Damien Neil <dneil@google.com> | 2025-04-01 15:43:22 -0700 |
|---|---|---|
| committer | Gopher Robot <gobot@golang.org> | 2025-05-07 13:14:25 -0700 |
| commit | d6c8bedc7b3d2de7714dca75bd05912b371538f1 (patch) | |
| tree | 352d43791d26d75bed756a7b02ef69b872483872 /src/runtime | |
| parent | 4e63ae46e097062b72424b2ac1da6e7dac33064b (diff) | |
| download | go-d6c8bedc7b3d2de7714dca75bd05912b371538f1.tar.xz | |
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>
Diffstat (limited to 'src/runtime')
| -rw-r--r-- | src/runtime/synctest.go | 34 |
1 files changed, 26 insertions, 8 deletions
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 } |
