Skip to content

Commit abc4f09

Browse files
[release-branch.go1.17] runtime: in adjustTimers back up as far as necessary
When the adjustTimers function removed a timer it assumed it was sufficient to continue the heap traversal at that position. However, in some cases a timer will be moved to an earlier position in the heap. If that timer is timerModifiedEarlier, that can leave timerModifiedEarliest not correctly representing the earlier such timer. Fix the problem by restarting the heap traversal at the earliest changed position. For #47762 Fixes #47859 Change-Id: I152bbe62793ee40a680baf49967bcb89b1f94764 Reviewed-on: https://go-review.googlesource.com/c/go/+/343882 Trust: Ian Lance Taylor <[email protected]> Run-TryBot: Ian Lance Taylor <[email protected]> TryBot-Result: Go Bot <[email protected]> Reviewed-by: Michael Knyszek <[email protected]> (cherry picked from commit 2da3375) Reviewed-on: https://go-review.googlesource.com/c/go/+/350001
1 parent e79c297 commit abc4f09

File tree

2 files changed

+87
-10
lines changed

2 files changed

+87
-10
lines changed

src/runtime/time.go

+20-10
Original file line numberDiff line numberDiff line change
@@ -366,9 +366,9 @@ func deltimer(t *timer) bool {
366366

367367
// dodeltimer removes timer i from the current P's heap.
368368
// We are locked on the P when this is called.
369-
// It reports whether it saw no problems due to races.
369+
// It returns the smallest changed index in pp.timers.
370370
// The caller must have locked the timers for pp.
371-
func dodeltimer(pp *p, i int) {
371+
func dodeltimer(pp *p, i int) int {
372372
if t := pp.timers[i]; t.pp.ptr() != pp {
373373
throw("dodeltimer: wrong P")
374374
} else {
@@ -380,16 +380,18 @@ func dodeltimer(pp *p, i int) {
380380
}
381381
pp.timers[last] = nil
382382
pp.timers = pp.timers[:last]
383+
smallestChanged := i
383384
if i != last {
384385
// Moving to i may have moved the last timer to a new parent,
385386
// so sift up to preserve the heap guarantee.
386-
siftupTimer(pp.timers, i)
387+
smallestChanged = siftupTimer(pp.timers, i)
387388
siftdownTimer(pp.timers, i)
388389
}
389390
if i == 0 {
390391
updateTimer0When(pp)
391392
}
392393
atomic.Xadd(&pp.numTimers, -1)
394+
return smallestChanged
393395
}
394396

395397
// dodeltimer0 removes timer 0 from the current P's heap.
@@ -674,13 +676,14 @@ func adjusttimers(pp *p, now int64) {
674676
switch s := atomic.Load(&t.status); s {
675677
case timerDeleted:
676678
if atomic.Cas(&t.status, s, timerRemoving) {
677-
dodeltimer(pp, i)
679+
changed := dodeltimer(pp, i)
678680
if !atomic.Cas(&t.status, timerRemoving, timerRemoved) {
679681
badTimer()
680682
}
681683
atomic.Xadd(&pp.deletedTimers, -1)
682-
// Look at this heap position again.
683-
i--
684+
// Go back to the earliest changed heap entry.
685+
// "- 1" because the loop will add 1.
686+
i = changed - 1
684687
}
685688
case timerModifiedEarlier, timerModifiedLater:
686689
if atomic.Cas(&t.status, s, timerMoving) {
@@ -690,10 +693,11 @@ func adjusttimers(pp *p, now int64) {
690693
// We don't add it back yet because the
691694
// heap manipulation could cause our
692695
// loop to skip some other timer.
693-
dodeltimer(pp, i)
696+
changed := dodeltimer(pp, i)
694697
moved = append(moved, t)
695-
// Look at this heap position again.
696-
i--
698+
// Go back to the earliest changed heap entry.
699+
// "- 1" because the loop will add 1.
700+
i = changed - 1
697701
}
698702
case timerNoStatus, timerRunning, timerRemoving, timerRemoved, timerMoving:
699703
badTimer()
@@ -1043,7 +1047,10 @@ func timeSleepUntil() (int64, *p) {
10431047
// "panic holding locks" message. Instead, we panic while not
10441048
// holding a lock.
10451049

1046-
func siftupTimer(t []*timer, i int) {
1050+
// siftupTimer puts the timer at position i in the right place
1051+
// in the heap by moving it up toward the top of the heap.
1052+
// It returns the smallest changed index.
1053+
func siftupTimer(t []*timer, i int) int {
10471054
if i >= len(t) {
10481055
badTimer()
10491056
}
@@ -1063,8 +1070,11 @@ func siftupTimer(t []*timer, i int) {
10631070
if tmp != t[i] {
10641071
t[i] = tmp
10651072
}
1073+
return i
10661074
}
10671075

1076+
// siftdownTimer puts the timer at position i in the right place
1077+
// in the heap by moving it down toward the bottom of the heap.
10681078
func siftdownTimer(t []*timer, i int) {
10691079
n := len(t)
10701080
if i >= n {

src/time/sleep_test.go

+67
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@ package time_test
77
import (
88
"errors"
99
"fmt"
10+
"math/rand"
1011
"runtime"
1112
"strings"
1213
"sync"
@@ -561,6 +562,72 @@ func TestTimerModifiedEarlier(t *testing.T) {
561562
}
562563
}
563564

565+
// Test that rapidly moving timers earlier and later doesn't cause
566+
// some of the sleep times to be lost.
567+
// Issue 47762
568+
func TestAdjustTimers(t *testing.T) {
569+
var rnd = rand.New(rand.NewSource(Now().UnixNano()))
570+
571+
timers := make([]*Timer, 100)
572+
states := make([]int, len(timers))
573+
indices := rnd.Perm(len(timers))
574+
575+
for len(indices) != 0 {
576+
var ii = rnd.Intn(len(indices))
577+
var i = indices[ii]
578+
579+
var timer = timers[i]
580+
var state = states[i]
581+
states[i]++
582+
583+
switch state {
584+
case 0:
585+
timers[i] = NewTimer(0)
586+
case 1:
587+
<-timer.C // Timer is now idle.
588+
589+
// Reset to various long durations, which we'll cancel.
590+
case 2:
591+
if timer.Reset(1 * Minute) {
592+
panic("shouldn't be active (1)")
593+
}
594+
case 4:
595+
if timer.Reset(3 * Minute) {
596+
panic("shouldn't be active (3)")
597+
}
598+
case 6:
599+
if timer.Reset(2 * Minute) {
600+
panic("shouldn't be active (2)")
601+
}
602+
603+
// Stop and drain a long-duration timer.
604+
case 3, 5, 7:
605+
if !timer.Stop() {
606+
t.Logf("timer %d state %d Stop returned false", i, state)
607+
<-timer.C
608+
}
609+
610+
// Start a short-duration timer we expect to select without blocking.
611+
case 8:
612+
if timer.Reset(0) {
613+
t.Fatal("timer.Reset returned true")
614+
}
615+
case 9:
616+
now := Now()
617+
<-timer.C
618+
dur := Since(now)
619+
if dur > 750*Millisecond {
620+
t.Errorf("timer %d took %v to complete", i, dur)
621+
}
622+
623+
// Timer is done. Swap with tail and remove.
624+
case 10:
625+
indices[ii] = indices[len(indices)-1]
626+
indices = indices[:len(indices)-1]
627+
}
628+
}
629+
}
630+
564631
// Benchmark timer latency when the thread that creates the timer is busy with
565632
// other work and the timers must be serviced by other threads.
566633
// https://golang.org/issue/38860

0 commit comments

Comments
 (0)