Skip to content

Commit 2da3375

Browse files
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. Fixes #47762 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]>
1 parent c7f2f51 commit 2da3375

File tree

2 files changed

+87
-10
lines changed

2 files changed

+87
-10
lines changed

src/runtime/time.go

Lines changed: 20 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -367,9 +367,9 @@ func deltimer(t *timer) bool {
367367

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

396398
// dodeltimer0 removes timer 0 from the current P's heap.
@@ -675,13 +677,14 @@ func adjusttimers(pp *p, now int64) {
675677
switch s := atomic.Load(&t.status); s {
676678
case timerDeleted:
677679
if atomic.Cas(&t.status, s, timerRemoving) {
678-
dodeltimer(pp, i)
680+
changed := dodeltimer(pp, i)
679681
if !atomic.Cas(&t.status, timerRemoving, timerRemoved) {
680682
badTimer()
681683
}
682684
atomic.Xadd(&pp.deletedTimers, -1)
683-
// Look at this heap position again.
684-
i--
685+
// Go back to the earliest changed heap entry.
686+
// "- 1" because the loop will add 1.
687+
i = changed - 1
685688
}
686689
case timerModifiedEarlier, timerModifiedLater:
687690
if atomic.Cas(&t.status, s, timerMoving) {
@@ -691,10 +694,11 @@ func adjusttimers(pp *p, now int64) {
691694
// We don't add it back yet because the
692695
// heap manipulation could cause our
693696
// loop to skip some other timer.
694-
dodeltimer(pp, i)
697+
changed := dodeltimer(pp, i)
695698
moved = append(moved, t)
696-
// Look at this heap position again.
697-
i--
699+
// Go back to the earliest changed heap entry.
700+
// "- 1" because the loop will add 1.
701+
i = changed - 1
698702
}
699703
case timerNoStatus, timerRunning, timerRemoving, timerRemoved, timerMoving:
700704
badTimer()
@@ -1044,7 +1048,10 @@ func timeSleepUntil() (int64, *p) {
10441048
// "panic holding locks" message. Instead, we panic while not
10451049
// holding a lock.
10461050

1047-
func siftupTimer(t []*timer, i int) {
1051+
// siftupTimer puts the timer at position i in the right place
1052+
// in the heap by moving it up toward the top of the heap.
1053+
// It returns the smallest changed index.
1054+
func siftupTimer(t []*timer, i int) int {
10481055
if i >= len(t) {
10491056
badTimer()
10501057
}
@@ -1064,8 +1071,11 @@ func siftupTimer(t []*timer, i int) {
10641071
if tmp != t[i] {
10651072
t[i] = tmp
10661073
}
1074+
return i
10671075
}
10681076

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

src/time/sleep_test.go

Lines changed: 67 additions & 0 deletions
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)