mirror of
https://github.com/golang/go.git
synced 2025-12-08 06:10:04 +00:00
runtime: prevent time.Timer.Reset(0) from deadlocking testing/synctest tests
In Go 1.23+, timer channels behave synchronously. When we have a timer channel (i.e. !async && t.isChan) we would lock the runtime.timer.sendLock mutex at the beginning of runtime.timer.modify()'s execution. Calling time.Timer.Reset(0) within a testing/synctest test, unfortunately, causes it to hang indefinitely. This is because the runtime.timer.sendLock mutex ends up being locked twice before it could be unlocked: - When calling time.Timer.Reset(), runtime.timer.modify() would lock the mutex per usual. - Due to the 0 argument, runtime.timer.modify() would also try to execute the bubbled timer immediately rather than adding them to a heap. However, in doing so, it uses runtime.timer.unlockAndRun(), which also locks the same mutex. This CL solves this issue by making sure that a locked runtime.timer.sendLock mutex is unlocked first, whenever we try to execute bubbled timer immediately in the stack. Fixes #76052 Change-Id: I66429b9bf6971400de95dcf2d5dc9670c3135492 Reviewed-on: https://go-review.googlesource.com/c/go/+/716883 Reviewed-by: Damien Neil <dneil@google.com> Auto-Submit: Nicholas Husin <nsh@golang.org> Reviewed-by: Nicholas Husin <husin@google.com> LUCI-TryBot-Result: Go LUCI <golang-scoped@luci-project-accounts.iam.gserviceaccount.com>
This commit is contained in:
parent
99b724f454
commit
385dc33250
2 changed files with 29 additions and 0 deletions
|
|
@ -5,6 +5,7 @@
|
||||||
package synctest_test
|
package synctest_test
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"context"
|
||||||
"fmt"
|
"fmt"
|
||||||
"internal/synctest"
|
"internal/synctest"
|
||||||
"internal/testenv"
|
"internal/testenv"
|
||||||
|
|
@ -329,6 +330,31 @@ func TestAfterFuncRunsImmediately(t *testing.T) {
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// TestTimerResetZeroDoNotHang verifies that using timer.Reset(0) does not
|
||||||
|
// cause the test to hang indefinitely. See https://go.dev/issue/76052.
|
||||||
|
func TestTimerResetZeroDoNotHang(t *testing.T) {
|
||||||
|
synctest.Run(func() {
|
||||||
|
timer := time.NewTimer(0)
|
||||||
|
ctx, cancel := context.WithCancel(context.Background())
|
||||||
|
|
||||||
|
go func() {
|
||||||
|
for {
|
||||||
|
select {
|
||||||
|
case <-ctx.Done():
|
||||||
|
return
|
||||||
|
case <-timer.C:
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}()
|
||||||
|
|
||||||
|
synctest.Wait()
|
||||||
|
timer.Reset(0)
|
||||||
|
synctest.Wait()
|
||||||
|
cancel()
|
||||||
|
synctest.Wait()
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
func TestChannelFromOutsideBubble(t *testing.T) {
|
func TestChannelFromOutsideBubble(t *testing.T) {
|
||||||
choutside := make(chan struct{})
|
choutside := make(chan struct{})
|
||||||
for _, test := range []struct {
|
for _, test := range []struct {
|
||||||
|
|
|
||||||
|
|
@ -636,6 +636,9 @@ func (t *timer) modify(when, period int64, f func(arg any, seq uintptr, delay in
|
||||||
}
|
}
|
||||||
if t.state&timerHeaped == 0 && when <= bubble.now {
|
if t.state&timerHeaped == 0 && when <= bubble.now {
|
||||||
systemstack(func() {
|
systemstack(func() {
|
||||||
|
if !async && t.isChan {
|
||||||
|
unlock(&t.sendLock)
|
||||||
|
}
|
||||||
t.unlockAndRun(bubble.now, bubble)
|
t.unlockAndRun(bubble.now, bubble)
|
||||||
})
|
})
|
||||||
return pending
|
return pending
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue