aboutsummaryrefslogtreecommitdiff
path: root/src/internal
diff options
context:
space:
mode:
authorDamien Neil <dneil@google.com>2025-06-02 09:26:27 -0700
committerGopher Robot <gobot@golang.org>2025-06-04 09:20:21 -0700
commit3432c68467d50ffc622fed230a37cd401d82d4bf (patch)
tree68e8152e4ba53aef6cb685d95ccc891b14d8c272 /src/internal
parent1aa336209363d9715e145244c7b22620ac0f0584 (diff)
downloadgo-3432c68467d50ffc622fed230a37cd401d82d4bf.tar.xz
runtime: make bubbled timers more consistent with unbubbled
This CL makes two changes to reduce the predictability with which bubbled timers fire. When asynctimerchan=0 (the default), regular timers with an associated channel are only added to a timer heap when some channel operation is blocked on that channel. This allows us to garbage collect unreferenced, unstopped timers. Timers in a synctest bubble, in contrast, are always added to the bubble's timer heap. This CL changes bubbled timers with a channel to be handled the same as unbubbled ones, adding them to the bubble's timer heap only when some channel operation is blocked on the timer's channel. This permits unstopped bubbled timers to be garbage collected, but more importantly it makes all timers past their deadline behave identically, regardless of whether they are in a bubble. This CL also changes timer scheduling to execute bubbled timers immediately when possible rather than adding them to a heap. Timers in a bubble's heap are executed when the bubble is idle. Executing timers immediately avoids creating a predictable order of execution. For #73850 Fixes #73934 Change-Id: If82e441546408f780f6af6fb7f6e416d3160295d Reviewed-on: https://go-review.googlesource.com/c/go/+/678075 Auto-Submit: Damien Neil <dneil@google.com> Reviewed-by: Michael Pratt <mpratt@google.com> LUCI-TryBot-Result: Go LUCI <golang-scoped@luci-project-accounts.iam.gserviceaccount.com>
Diffstat (limited to 'src/internal')
-rw-r--r--src/internal/synctest/synctest_test.go55
1 files changed, 53 insertions, 2 deletions
diff --git a/src/internal/synctest/synctest_test.go b/src/internal/synctest/synctest_test.go
index 53c7c89716..c2f84be736 100644
--- a/src/internal/synctest/synctest_test.go
+++ b/src/internal/synctest/synctest_test.go
@@ -226,8 +226,8 @@ func TestTimerNondeterminism(t *testing.T) {
const iterations = 1000
var seen1, seen2 bool
for range iterations {
- tm1 := time.NewTimer(0)
- tm2 := time.NewTimer(0)
+ tm1 := time.NewTimer(1)
+ tm2 := time.NewTimer(1)
select {
case <-tm1.C:
seen1 = true
@@ -278,6 +278,57 @@ func TestSleepNondeterminism(t *testing.T) {
})
}
+// TestTimerRunsImmediately verifies that a 0-duration timer sends on its channel
+// without waiting for the bubble to block.
+func TestTimerRunsImmediately(t *testing.T) {
+ synctest.Run(func() {
+ start := time.Now()
+ tm := time.NewTimer(0)
+ select {
+ case got := <-tm.C:
+ if !got.Equal(start) {
+ t.Errorf("<-tm.C = %v, want %v", got, start)
+ }
+ default:
+ t.Errorf("0-duration timer channel is not readable; want it to be")
+ }
+ })
+}
+
+// TestTimerRunsLater verifies that reading from a timer's channel receives the
+// timer fired, even when that time is in reading from a timer's channel receives the
+// time the timer fired, even when that time is in the past.
+func TestTimerRanInPast(t *testing.T) {
+ synctest.Run(func() {
+ delay := 1 * time.Second
+ want := time.Now().Add(delay)
+ tm := time.NewTimer(delay)
+ time.Sleep(2 * delay)
+ select {
+ case got := <-tm.C:
+ if !got.Equal(want) {
+ t.Errorf("<-tm.C = %v, want %v", got, want)
+ }
+ default:
+ t.Errorf("0-duration timer channel is not readable; want it to be")
+ }
+ })
+}
+
+// TestAfterFuncRunsImmediately verifies that a 0-duration AfterFunc is scheduled
+// without waiting for the bubble to block.
+func TestAfterFuncRunsImmediately(t *testing.T) {
+ synctest.Run(func() {
+ var b atomic.Bool
+ time.AfterFunc(0, func() {
+ b.Store(true)
+ })
+ for !b.Load() {
+ runtime.Gosched()
+ }
+ })
+}
+
func TestChannelFromOutsideBubble(t *testing.T) {
choutside := make(chan struct{})
for _, test := range []struct {