})
}
+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() {
})
}
+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
package runtime
import (
+ "internal/runtime/sys"
"unsafe"
)
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.
//
case _Gdead:
isRunning = false
totalDelta--
+ if gp == sg.main {
+ sg.done = true
+ }
case _Gwaiting:
if gp.waitreason.isIdleInSynctest() {
isRunning = false
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 {
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
}
// 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