Skip to content

Commit 641e61d

Browse files
runtime: don't let P's timer heap get clogged with deleted timers
Whenever more than 1/4 of the timers on a P's heap are deleted, remove them from the heap. Change-Id: Iff63ed3d04e6f33ffc5c834f77f645c52c007e52 Reviewed-on: https://go-review.googlesource.com/c/go/+/214299 Run-TryBot: Ian Lance Taylor <[email protected]> TryBot-Result: Gobot Gobot <[email protected]> Reviewed-by: Michael Knyszek <[email protected]>
1 parent 1d4d782 commit 641e61d

File tree

3 files changed

+134
-2
lines changed

3 files changed

+134
-2
lines changed

src/runtime/proc.go

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2643,6 +2643,13 @@ func checkTimers(pp *p, now int64) (rnow, pollUntil int64, ran bool) {
26432643
}
26442644
}
26452645

2646+
// If this is the local P, and there are a lot of deleted timers,
2647+
// clear them out. We only do this for the local P to reduce
2648+
// lock contention on timersLock.
2649+
if pp == getg().m.p.ptr() && int(atomic.Load(&pp.deletedTimers)) > len(pp.timers)/4 {
2650+
clearDeletedTimers(pp)
2651+
}
2652+
26462653
unlock(&pp.timersLock)
26472654

26482655
return rnow, pollUntil, ran
@@ -4087,6 +4094,7 @@ func (pp *p) destroy() {
40874094
moveTimers(plocal, pp.timers)
40884095
pp.timers = nil
40894096
pp.adjustTimers = 0
4097+
pp.deletedTimers = 0
40904098
unlock(&pp.timersLock)
40914099
unlock(&plocal.timersLock)
40924100
}

src/runtime/runtime2.go

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -650,6 +650,10 @@ type p struct {
650650
// such as timerModifying.
651651
adjustTimers uint32
652652

653+
// Number of timerDeleted times in P's heap.
654+
// Modified using atomic instructions.
655+
deletedTimers uint32
656+
653657
// Race context used while executing timer functions.
654658
timerRaceCtx uintptr
655659

src/runtime/time.go

Lines changed: 122 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -169,6 +169,10 @@ const (
169169
// maxWhen is the maximum value for timer's when field.
170170
const maxWhen = 1<<63 - 1
171171

172+
// verifyTimers can be set to true to add debugging checks that the
173+
// timer heaps are valid.
174+
const verifyTimers = false
175+
172176
// Package time APIs.
173177
// Godoc uses the comments in package time, not these.
174178

@@ -295,7 +299,9 @@ func deltimer(t *timer) bool {
295299
for {
296300
switch s := atomic.Load(&t.status); s {
297301
case timerWaiting, timerModifiedLater:
302+
tpp := t.pp.ptr()
298303
if atomic.Cas(&t.status, s, timerDeleted) {
304+
atomic.Xadd(&tpp.deletedTimers, 1)
299305
// Timer was not yet run.
300306
return true
301307
}
@@ -306,6 +312,7 @@ func deltimer(t *timer) bool {
306312
if !atomic.Cas(&t.status, timerModifying, timerDeleted) {
307313
badTimer()
308314
}
315+
atomic.Xadd(&tpp.deletedTimers, 1)
309316
// Timer was not yet run.
310317
return true
311318
}
@@ -486,6 +493,7 @@ func resettimer(t *timer, when int64) {
486493
return
487494
}
488495
case timerDeleted:
496+
tpp := t.pp.ptr()
489497
if atomic.Cas(&t.status, s, timerModifying) {
490498
t.nextwhen = when
491499
newStatus := uint32(timerModifiedLater)
@@ -496,6 +504,7 @@ func resettimer(t *timer, when int64) {
496504
if !atomic.Cas(&t.status, timerModifying, newStatus) {
497505
badTimer()
498506
}
507+
atomic.Xadd(&tpp.deletedTimers, -1)
499508
if newStatus == timerModifiedEarlier {
500509
wakeNetPoller(when)
501510
}
@@ -543,6 +552,7 @@ func cleantimers(pp *p) bool {
543552
if !atomic.Cas(&t.status, timerRemoving, timerRemoved) {
544553
return false
545554
}
555+
atomic.Xadd(&pp.deletedTimers, -1)
546556
case timerModifiedEarlier, timerModifiedLater:
547557
if !atomic.Cas(&t.status, s, timerMoving) {
548558
continue
@@ -631,9 +641,13 @@ func adjusttimers(pp *p) {
631641
return
632642
}
633643
if atomic.Load(&pp.adjustTimers) == 0 {
644+
if verifyTimers {
645+
verifyTimerHeap(pp.timers)
646+
}
634647
return
635648
}
636649
var moved []*timer
650+
loop:
637651
for i := 0; i < len(pp.timers); i++ {
638652
t := pp.timers[i]
639653
if t.pp.ptr() != pp {
@@ -648,6 +662,7 @@ func adjusttimers(pp *p) {
648662
if !atomic.Cas(&t.status, timerRemoving, timerRemoved) {
649663
badTimer()
650664
}
665+
atomic.Xadd(&pp.deletedTimers, -1)
651666
// Look at this heap position again.
652667
i--
653668
}
@@ -665,8 +680,7 @@ func adjusttimers(pp *p) {
665680
moved = append(moved, t)
666681
if s == timerModifiedEarlier {
667682
if n := atomic.Xadd(&pp.adjustTimers, -1); int32(n) <= 0 {
668-
addAdjustedTimers(pp, moved)
669-
return
683+
break loop
670684
}
671685
}
672686
// Look at this heap position again.
@@ -688,6 +702,10 @@ func adjusttimers(pp *p) {
688702
if len(moved) > 0 {
689703
addAdjustedTimers(pp, moved)
690704
}
705+
706+
if verifyTimers {
707+
verifyTimerHeap(pp.timers)
708+
}
691709
}
692710

693711
// addAdjustedTimers adds any timers we adjusted in adjusttimers
@@ -762,6 +780,7 @@ func runtimer(pp *p, now int64) int64 {
762780
if !atomic.Cas(&t.status, timerRemoving, timerRemoved) {
763781
badTimer()
764782
}
783+
atomic.Xadd(&pp.deletedTimers, -1)
765784
if len(pp.timers) == 0 {
766785
return -1
767786
}
@@ -859,6 +878,107 @@ func runOneTimer(pp *p, t *timer, now int64) {
859878
}
860879
}
861880

881+
// clearDeletedTimers removes all deleted timers from the P's timer heap.
882+
// This is used to avoid clogging up the heap if the program
883+
// starts a lot of long-running timers and then stops them.
884+
// For example, this can happen via context.WithTimeout.
885+
//
886+
// This is the only function that walks through the entire timer heap,
887+
// other than moveTimers which only runs when the world is stopped.
888+
//
889+
// The caller must have locked the timers for pp.
890+
func clearDeletedTimers(pp *p) {
891+
cdel := int32(0)
892+
cearlier := int32(0)
893+
to := 0
894+
changedHeap := false
895+
timers := pp.timers
896+
nextTimer:
897+
for _, t := range timers {
898+
for {
899+
switch s := atomic.Load(&t.status); s {
900+
case timerWaiting:
901+
if changedHeap {
902+
timers[to] = t
903+
siftupTimer(timers, to)
904+
}
905+
to++
906+
continue nextTimer
907+
case timerModifiedEarlier, timerModifiedLater:
908+
if atomic.Cas(&t.status, s, timerMoving) {
909+
t.when = t.nextwhen
910+
timers[to] = t
911+
siftupTimer(timers, to)
912+
to++
913+
changedHeap = true
914+
if !atomic.Cas(&t.status, timerMoving, timerWaiting) {
915+
badTimer()
916+
}
917+
if s == timerModifiedEarlier {
918+
cearlier++
919+
}
920+
continue nextTimer
921+
}
922+
case timerDeleted:
923+
if atomic.Cas(&t.status, s, timerRemoving) {
924+
t.pp = 0
925+
cdel++
926+
if !atomic.Cas(&t.status, timerRemoving, timerRemoved) {
927+
badTimer()
928+
}
929+
changedHeap = true
930+
continue nextTimer
931+
}
932+
case timerModifying:
933+
// Loop until modification complete.
934+
osyield()
935+
case timerNoStatus, timerRemoved:
936+
// We should not see these status values in a timer heap.
937+
badTimer()
938+
case timerRunning, timerRemoving, timerMoving:
939+
// Some other P thinks it owns this timer,
940+
// which should not happen.
941+
badTimer()
942+
default:
943+
badTimer()
944+
}
945+
}
946+
}
947+
948+
// Set remaining slots in timers slice to nil,
949+
// so that the timer values can be garbage collected.
950+
for i := to; i < len(timers); i++ {
951+
timers[i] = nil
952+
}
953+
954+
timers = timers[:to]
955+
if verifyTimers {
956+
verifyTimerHeap(timers)
957+
}
958+
pp.timers = timers
959+
atomic.Xadd(&pp.deletedTimers, -cdel)
960+
atomic.Xadd(&pp.adjustTimers, -cearlier)
961+
}
962+
963+
// verifyTimerHeap verifies that the timer heap is in a valid state.
964+
// This is only for debugging, and is only called if verifyTimers is true.
965+
// The caller must have locked the timers.
966+
func verifyTimerHeap(timers []*timer) {
967+
for i, t := range timers {
968+
if i == 0 {
969+
// First timer has no parent.
970+
continue
971+
}
972+
973+
// The heap is 4-ary. See siftupTimer and siftdownTimer.
974+
p := (i - 1) / 4
975+
if t.when < timers[p].when {
976+
print("bad timer heap at ", i, ": ", p, ": ", timers[p].when, ", ", i, ": ", t.when, "\n")
977+
throw("bad timer heap")
978+
}
979+
}
980+
}
981+
862982
func timejump() *p {
863983
if faketime == 0 {
864984
return nil

0 commit comments

Comments
 (0)