}
func TestHappensBefore(t *testing.T) {
- var v int
+ // Use two parallel goroutines accessing different vars to ensure that
+ // we correctly account for multiple goroutines in the bubble.
+ var v1 int
+ var v2 int
synctest.Run(func() {
+ v1++ // 1
+ v2++ // 1
+
+ // Wait returns after these goroutines exit.
+ go func() {
+ v1++ // 2
+ }()
go func() {
- v++ // 1
+ v2++ // 2
}()
- // Wait creates a happens-before relationship on the above goroutine exiting.
synctest.Wait()
+
+ v1++ // 3
+ v2++ // 3
+
+ // Wait returns after these goroutines block.
+ ch1 := make(chan struct{})
go func() {
- v++ // 2
+ v1++ // 4
+ <-ch1
}()
- })
- // Run exiting creates a happens-before relationship on goroutines started in the bubble.
- synctest.Run(func() {
- v++ // 3
- // There is a happens-before relationship between the time.AfterFunc call,
- // and the func running.
+ go func() {
+ v2++ // 4
+ <-ch1
+ }()
+ synctest.Wait()
+
+ v1++ // 5
+ v2++ // 5
+ close(ch1)
+
+ // Wait returns after these timers run.
+ time.AfterFunc(0, func() {
+ v1++ // 6
+ })
+ time.AfterFunc(0, func() {
+ v2++ // 6
+ })
+ synctest.Wait()
+
+ v1++ // 7
+ v2++ // 7
+
+ // Wait returns after these timer goroutines block.
+ ch2 := make(chan struct{})
time.AfterFunc(0, func() {
- v++ // 4
+ v1++ // 8
+ <-ch2
})
+ time.AfterFunc(0, func() {
+ v2++ // 8
+ <-ch2
+ })
+ synctest.Wait()
+
+ v1++ // 9
+ v2++ // 9
+ close(ch2)
})
- if got, want := v, 4; got != want {
- t.Errorf("v = %v, want %v", got, want)
+ // This Run happens after the previous Run returns.
+ synctest.Run(func() {
+ go func() {
+ go func() {
+ v1++ // 10
+ }()
+ }()
+ go func() {
+ go func() {
+ v2++ // 10
+ }()
+ }()
+ })
+ // These tests happen after Run returns.
+ if got, want := v1, 10; got != want {
+ t.Errorf("v1 = %v, want %v", got, want)
+ }
+ if got, want := v2, 10; got != want {
+ t.Errorf("v2 = %v, want %v", got, want)
}
}
--- /dev/null
+// Copyright 2025 The Go Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style
+// license that can be found in the LICENSE file.
+
+package race_test
+
+import (
+ "internal/synctest"
+ "testing"
+ "time"
+)
+
+func TestRaceSynctestGoroutinesExit(t *testing.T) {
+ synctest.Run(func() {
+ x := 0
+ _ = x
+ f := func() {
+ x = 1
+ }
+ go f()
+ go f()
+ })
+}
+
+func TestNoRaceSynctestGoroutinesExit(t *testing.T) {
+ synctest.Run(func() {
+ x := 0
+ _ = x
+ f := func() {
+ x = 1
+ }
+ go f()
+ synctest.Wait()
+ go f()
+ })
+}
+
+func TestRaceSynctestGoroutinesRecv(t *testing.T) {
+ synctest.Run(func() {
+ x := 0
+ _ = x
+ ch := make(chan struct{})
+ f := func() {
+ x = 1
+ <-ch
+ }
+ go f()
+ go f()
+ close(ch)
+ })
+}
+
+func TestRaceSynctestGoroutinesUnblocked(t *testing.T) {
+ synctest.Run(func() {
+ x := 0
+ _ = x
+ ch := make(chan struct{})
+ f := func() {
+ <-ch
+ x = 1
+ }
+ go f()
+ go f()
+ close(ch)
+ })
+}
+
+func TestRaceSynctestGoroutinesSleep(t *testing.T) {
+ synctest.Run(func() {
+ x := 0
+ _ = x
+ go func() {
+ time.Sleep(1 * time.Second)
+ x = 1
+ time.Sleep(2 * time.Second)
+ }()
+ go func() {
+ time.Sleep(2 * time.Second)
+ x = 1
+ time.Sleep(1 * time.Second)
+ }()
+ time.Sleep(5 * time.Second)
+ })
+}
+
+func TestRaceSynctestTimers(t *testing.T) {
+ synctest.Run(func() {
+ x := 0
+ _ = x
+ f := func() {
+ x = 1
+ }
+ time.AfterFunc(1*time.Second, f)
+ time.AfterFunc(2*time.Second, f)
+ time.Sleep(5 * time.Second)
+ })
+}
} else {
sg.running--
if raceenabled && newval != _Gdead {
+ // Record that this goroutine parking happens before
+ // any subsequent Wait.
racereleasemergeg(gp, sg.raceaddr())
}
}
// Two wakes happening at the same time leads to very confusing failure modes,
// so we take steps to avoid it happening.
sg.active++
+ next := sg.timers.wakeTime()
+ if next > 0 && next <= sg.now {
+ // A timer is scheduled to fire. Wake the root goroutine to handle it.
+ return sg.root
+ }
if gp := sg.waiter; gp != nil {
// A goroutine is blocked in Wait. Wake it.
return gp
lock(&sg.mu)
sg.active++
for {
- if raceenabled {
- // Establish a happens-before relationship between a timer being created,
- // and the timer running.
- raceacquireg(gp, gp.syncGroup.raceaddr())
- }
unlock(&sg.mu)
systemstack(func() {
+ // Clear gp.m.curg while running timers,
+ // so timer goroutines inherit their child race context from g0.
+ curg := gp.m.curg
+ gp.m.curg = nil
gp.syncGroup.timers.check(gp.syncGroup.now)
+ gp.m.curg = curg
})
gopark(synctestidle_c, nil, waitReasonSynctestRun, traceBlockSynctest, 0)
lock(&sg.mu)
// Note that we are running on a system stack,
// so there is no chance of getg().m being reassigned
// out from under us while this function executes.
- tsLocal := &getg().m.p.ptr().timers
+ gp := getg()
+ var tsLocal *timers
+ if t.ts == nil || t.ts.syncGroup == nil {
+ tsLocal = &gp.m.p.ptr().timers
+ } else {
+ tsLocal = &t.ts.syncGroup.timers
+ }
if tsLocal.raceCtx == 0 {
tsLocal.raceCtx = racegostart(abi.FuncPCABIInternal((*timers).run) + sys.PCQuantum)
}
if gp.racectx != 0 {
throw("unexpected racectx")
}
- gp.racectx = gp.m.p.ptr().timers.raceCtx
+ if ts == nil || ts.syncGroup == nil {
+ gp.racectx = gp.m.p.ptr().timers.raceCtx
+ } else {
+ gp.racectx = ts.syncGroup.timers.raceCtx
+ }
}
if ts != nil {
if ts != nil && ts.syncGroup != nil {
gp := getg()
ts.syncGroup.changegstatus(gp, _Grunning, _Gdead)
+ if raceenabled {
+ // Establish a happens-before between this timer event and
+ // the next synctest.Wait call.
+ racereleasemergeg(gp, ts.syncGroup.raceaddr())
+ }
gp.syncGroup = nil
}