-
Notifications
You must be signed in to change notification settings - Fork 18.5k
Description
The problem was discovered in kubernetes/kubernetes#88638, where Kubernetes was seeing some kubelets get wedged. They grabbed a stack trace for me, and after combing over it I summarized the problem. Below are the highlights:
[...] There's a self-deadlock going on in the timer system.
- A goroutine does:
timer.Reset -> resettimer -> modtimer -> wakeNetPoller
- At this point, the timer its resetting is in the
timerModifyingstate.- Now, at the beginning of
wakeNetPoller, we get a preemption request (synchronous, so unrelated to asynchronous preemption), so we call intomorestack.- The chain now goes
morestack -> gopreempt_m -> goschedImpl -> schedule -> checkTimers -> runtimer- Now we try to run the timer we were modifying, but it's currently being modified, so we loop and
osyield. It never stops being intimerModifying, though.
(kubernetes/kubernetes#88638 (comment))
[...] this can only happen if the timer is in the
timerDeletedand at the top of the heap, and indeed(*time.Timer).Resetdoes stop (delete) the timer before resetting it. So, this doesn't imply a racy use of timers on Kubernetes' end.Looking at the state machine in
time.goagain, [...] it could only ever happen with thewakeNetPollercall, because inaddInitializedTimerwe grab a runtime lock aroundcleantimersanddoaddtimer, which prevents preemption.Maybe what happened is that we accidentally inserted a preemption point? What if it was the case that in
resettimerbeforewakeNetPollerwas actually inlined, but inaddInitializedTimerit's not. This means we added in a preemption point unintentionally.One idea for the fix is to just... make sure
wakeNetPollergets inlined (and maybe https://go-review.googlesource.com/c/go/+/224902 actually does that), but that seems fragile. We should probably say that while a goroutine owns a timer intimerModifyingit should not be allowed to be preempted, because it can cause an operation that waits (perhaps the same for other-ingstates).