Skip to content
Snippets Groups Projects
Commit d6c8bedc authored by Damien Neil's avatar Damien Neil Committed by Gopher Robot
Browse files

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: default avatarMichael Pratt <mpratt@google.com>
LUCI-TryBot-Result: Go LUCI <golang-scoped@luci-project-accounts.iam.gserviceaccount.com>
Auto-Submit: Damien Neil <dneil@google.com>
parent 4e63ae46
No related branches found
No related tags found
No related merge requests found
......@@ -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) {
tm := time.NewTimer(10 * time.Millisecond)
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) {
synctest.Run(func() {
var mu sync.Mutex
......
......@@ -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
}
......
......@@ -28,7 +28,10 @@ import (
// goroutines are blocked and return after the bubble's clock has
// 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.
//
// Channels, time.Timers, and time.Tickers created within the bubble
......
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Please register or to comment