diff options
| author | Damien Neil <dneil@google.com> | 2025-03-17 13:48:50 -0700 |
|---|---|---|
| committer | Gopher Robot <gobot@golang.org> | 2025-03-21 09:32:55 -0700 |
| commit | c0392e7e494c7e1fa7122df3cb5c1a30760ac5b4 (patch) | |
| tree | 444df3202abcf05da541fed7eddf85ab8ed571ca /src/runtime/race/testdata | |
| parent | 69ea62fe95a14a04d6b2ba145aaf78e36f42e2eb (diff) | |
| download | go-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.go | 97 |
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) + }) +} |
