From e38be310a4f725ce9167b3444eedcd3b15a6e683 Mon Sep 17 00:00:00 2001 From: Russ Cox Date: Thu, 7 Mar 2024 13:51:04 -0500 Subject: [PATCH] time: gracefully handle ts.zombies underflow The current implementation sets t.ts before adding t to ts; that can cause inconsistencies with temporarily negative ts.zombies values. Handle them gracefully, since we only care about detecting very positive values. Pending CL 564977 removes the race that sets t.ts early, and then CL 569996 builds on top of that to make the count precise. This CL just gets examples like the new test working sooner. Change-Id: Ibe1aecc2554f83436f761f48e4050bd962982e4f Reviewed-on: https://go-review.googlesource.com/c/go/+/569995 Reviewed-by: Austin Clements LUCI-TryBot-Result: Go LUCI --- src/runtime/time.go | 2 +- src/time/tick_test.go | 78 +++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 79 insertions(+), 1 deletion(-) diff --git a/src/runtime/time.go b/src/runtime/time.go index 1899589795..65558cfcda 100644 --- a/src/runtime/time.go +++ b/src/runtime/time.go @@ -66,7 +66,7 @@ type timers struct { len atomic.Uint32 // zombies is the number of deleted timers left in heap. - zombies atomic.Uint32 + zombies atomic.Int32 // raceCtx is the race context used while executing timer functions. raceCtx uintptr diff --git a/src/time/tick_test.go b/src/time/tick_test.go index f539091869..9f1d366980 100644 --- a/src/time/tick_test.go +++ b/src/time/tick_test.go @@ -7,6 +7,7 @@ package time_test import ( "fmt" "runtime" + "sync" "testing" . "time" ) @@ -145,6 +146,83 @@ func TestTickerResetLtZeroDuration(t *testing.T) { tk.Reset(0) } +func TestLongAdjustTimers(t *testing.T) { + t.Parallel() + var wg sync.WaitGroup + defer wg.Wait() + + // Build up the timer heap. + const count = 5000 + wg.Add(count) + for range count { + go func() { + defer wg.Done() + Sleep(10 * Microsecond) + }() + } + for range count { + Sleep(1 * Microsecond) + } + + // Give ourselves 60 seconds to complete. + // This used to reliably fail on a Mac M3 laptop, + // which needed 77 seconds. + // Trybots are slower, so it will fail even more reliably there. + // With the fix, the code runs in under a second. + done := make(chan bool) + AfterFunc(60*Second, func() { close(done) }) + + // Set up a queing goroutine to ping pong through the scheduler. + inQ := make(chan func()) + outQ := make(chan func()) + + defer close(inQ) + + wg.Add(1) + go func() { + defer wg.Done() + defer close(outQ) + var q []func() + for { + var sendTo chan func() + var send func() + if len(q) > 0 { + sendTo = outQ + send = q[0] + } + select { + case sendTo <- send: + q = q[1:] + case f, ok := <-inQ: + if !ok { + return + } + q = append(q, f) + case <-done: + return + } + } + }() + + for i := range 50000 { + const try = 20 + for range try { + inQ <- func() {} + } + for range try { + select { + case _, ok := <-outQ: + if !ok { + t.Fatal("output channel is closed") + } + case <-After(5 * Second): + t.Fatalf("failed to read work, iteration %d", i) + case <-done: + t.Fatal("timer expired") + } + } + } +} func BenchmarkTicker(b *testing.B) { benchmark(b, func(n int) { ticker := NewTicker(Nanosecond) -- 2.48.1