]> Cypherpunks repositories - gostls13.git/commitdiff
time: gracefully handle ts.zombies underflow
authorRuss Cox <rsc@golang.org>
Thu, 7 Mar 2024 18:51:04 +0000 (13:51 -0500)
committerRuss Cox <rsc@golang.org>
Fri, 8 Mar 2024 19:41:32 +0000 (19:41 +0000)
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 <austin@google.com>
LUCI-TryBot-Result: Go LUCI <golang-scoped@luci-project-accounts.iam.gserviceaccount.com>

src/runtime/time.go
src/time/tick_test.go

index 1899589795a4ca395a8f82a87623f1febf49492a..65558cfcdabdd72b20d9bb971b964ac7bbd4a093 100644 (file)
@@ -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
index f539091869fcaf2f06bde50954608d2a229ee79c..9f1d366980f0645ac9574d8db41f4d6e2cc524d6 100644 (file)
@@ -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)