aboutsummaryrefslogtreecommitdiff
path: root/src/time
diff options
context:
space:
mode:
authorIan Lance Taylor <iant@golang.org>2024-09-06 17:19:34 -0700
committerGopher Robot <gobot@golang.org>2024-09-26 22:39:10 +0000
commit2ebaff4890596ed6064e2dcbbe5e68bc93bed882 (patch)
tree481ba1fd918c5c33e5d7224b3ff5b69f93ea5af0 /src/time
parent35874308993d5dbb3a618254babb5c1fa85bd1e3 (diff)
downloadgo-2ebaff4890596ed6064e2dcbbe5e68bc93bed882.tar.xz
runtime: if stop/reset races with running timer, return correct result
The timer code is careful to ensure that if stop/reset is called while a timer is being run, we cancel the run. However, the code failed to ensure that in that case stop/reset returned true, meaning that the timer had been stopped. In the racing case stop/reset could see that t.when had been set to zero, and return false, even though the timer had not and never would fire. Fix this by tracking whether a timer run is in progress, and using that to reliably detect that the run was cancelled, meaning that stop/reset should return true. Fixes #69312 Change-Id: I78e870063eb96650638f12c056e32c931417c84a Reviewed-on: https://go-review.googlesource.com/c/go/+/611496 Reviewed-by: David Chase <drchase@google.com> Reviewed-by: Cuong Manh Le <cuong.manhle.vn@gmail.com> Reviewed-by: Michael Knyszek <mknyszek@google.com> LUCI-TryBot-Result: Go LUCI <golang-scoped@luci-project-accounts.iam.gserviceaccount.com> Auto-Submit: Ian Lance Taylor <iant@golang.org>
Diffstat (limited to 'src/time')
-rw-r--r--src/time/sleep_test.go62
1 files changed, 62 insertions, 0 deletions
diff --git a/src/time/sleep_test.go b/src/time/sleep_test.go
index 29f56ef752..5357ed23c8 100644
--- a/src/time/sleep_test.go
+++ b/src/time/sleep_test.go
@@ -785,6 +785,68 @@ func TestAdjustTimers(t *testing.T) {
}
}
+func TestStopResult(t *testing.T) {
+ testStopResetResult(t, true)
+}
+
+func TestResetResult(t *testing.T) {
+ testStopResetResult(t, false)
+}
+
+// Test that when racing between running a timer and stopping a timer Stop
+// consistently indicates whether a value can be read from the channel.
+// Issue #69312.
+func testStopResetResult(t *testing.T, testStop bool) {
+ for _, name := range []string{"0", "1", "2"} {
+ t.Run("asynctimerchan="+name, func(t *testing.T) {
+ testStopResetResultGODEBUG(t, testStop, name)
+ })
+ }
+}
+
+func testStopResetResultGODEBUG(t *testing.T, testStop bool, godebug string) {
+ t.Setenv("GODEBUG", "asynctimerchan="+godebug)
+
+ stopOrReset := func(timer *Timer) bool {
+ if testStop {
+ return timer.Stop()
+ } else {
+ return timer.Reset(1 * Hour)
+ }
+ }
+
+ start := make(chan struct{})
+ var wg sync.WaitGroup
+ const N = 1000
+ wg.Add(N)
+ for range N {
+ go func() {
+ defer wg.Done()
+ <-start
+ for j := 0; j < 100; j++ {
+ timer1 := NewTimer(1 * Millisecond)
+ timer2 := NewTimer(1 * Millisecond)
+ select {
+ case <-timer1.C:
+ if !stopOrReset(timer2) {
+ // The test fails if this
+ // channel read times out.
+ <-timer2.C
+ }
+ case <-timer2.C:
+ if !stopOrReset(timer1) {
+ // The test fails if this
+ // channel read times out.
+ <-timer1.C
+ }
+ }
+ }
+ }()
+ }
+ close(start)
+ wg.Wait()
+}
+
// Benchmark timer latency when the thread that creates the timer is busy with
// other work and the timers must be serviced by other threads.
// https://golang.org/issue/38860