aboutsummaryrefslogtreecommitdiff
path: root/src/runtime/race/testdata
diff options
context:
space:
mode:
authorDamien Neil <dneil@google.com>2025-03-17 13:48:50 -0700
committerGopher Robot <gobot@golang.org>2025-03-21 09:32:55 -0700
commitc0392e7e494c7e1fa7122df3cb5c1a30760ac5b4 (patch)
tree444df3202abcf05da541fed7eddf85ab8ed571ca /src/runtime/race/testdata
parent69ea62fe95a14a04d6b2ba145aaf78e36f42e2eb (diff)
downloadgo-c0392e7e494c7e1fa7122df3cb5c1a30760ac5b4.tar.xz
runtime: fix interactions between synctest, race detector, and timers
When an AfterFunc executes in a synctest bubble, there is a series of happens-before relationships: 1. The AfterFunc is created. 2. The AfterFunc goroutine executes. 3. The AfterFunc goroutine returns. 4. A subsequent synctest.Wait call returns. We were failing to correctly establish the happens-before relationship between the AfterFunc goroutine and the AfterFunc itself being created. When an AfterFunc executes, the G running the timer temporarily switches to the timer heap's racectx. It then calls time.goFunc, which starts a new goroutine to execute the timer. time.goFunc relies on the new goroutine inheriting the racectx of the G running the timer. Normal, non-synctest timers, execute with m.curg == nil, which causes new goroutines to inherit the g0 racectx. We were running synctest timers with m.curg set (to the G executing synctest.Run), so the new AfterFunc goroutine was created using m.curg's racectx. This resulted in us not properly establishing the happens-before relationship between AfterFunc being called and the AfterFunc goroutine starting. Fix this by setting m.curg to nil while executing timers. As one additional fix, when waking a blocked bubble, wake the root goroutine rather than a goroutine blocked in Wait if there is a timer that can fire. Fixes #72750 Change-Id: I2b2d6b0f17f64649409adc93c2603f720494af89 Reviewed-on: https://go-review.googlesource.com/c/go/+/658595 Auto-Submit: Damien Neil <dneil@google.com> LUCI-TryBot-Result: Go LUCI <golang-scoped@luci-project-accounts.iam.gserviceaccount.com> Reviewed-by: Michael Pratt <mpratt@google.com>
Diffstat (limited to 'src/runtime/race/testdata')
-rw-r--r--src/runtime/race/testdata/synctest_test.go97
1 files changed, 97 insertions, 0 deletions
diff --git a/src/runtime/race/testdata/synctest_test.go b/src/runtime/race/testdata/synctest_test.go
new file mode 100644
index 0000000000..dfbd5682ca
--- /dev/null
+++ b/src/runtime/race/testdata/synctest_test.go
@@ -0,0 +1,97 @@
+// 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)
+ })
+}