Skip to content

Commit 2d8c199

Browse files
runtime: release timersLock while running timer
Dan Scales pointed out a theoretical deadlock in the runtime. The timer code runs timer functions while holding the timers lock for a P. The scavenger queues up a timer function that calls wakeScavenger, which acquires the scavenger lock. The scavengeSleep function acquires the scavenger lock, then calls resetTimer which can call addInitializedTimer which acquires the timers lock for the current P. So there is a potential deadlock, in that the scavenger lock and the timers lock for some P may both be acquired in different order. It's not clear to me whether this deadlock can ever actually occur. Issue 35532 describes another possible deadlock. The pollSetDeadline function acquires pd.lock for some poll descriptor, and in some cases calls resettimer which can in some cases acquire the timers lock for the current P. The timer code runs timer functions while holding the timers lock for a P. The timer function for poll descriptors winds up in netpolldeadlineimpl which acquires pd.lock. So again there is a potential deadlock, in that the pd lock for some poll descriptor and the timers lock for some P may both be acquired in different order. I think this can happen if we change the deadline for a network connection exactly as the former deadline expires. Looking at the code, I don't see any reason why we have to hold the timers lock while running a timer function. This CL implements that change. Updates #6239 Updates #27707 Fixes #35532 Change-Id: I17792f5a0120e01ea07cf1b2de8434d5c10704dd Reviewed-on: https://go-review.googlesource.com/c/go/+/207348 Run-TryBot: Ian Lance Taylor <[email protected]> TryBot-Result: Gobot Gobot <[email protected]> Reviewed-by: Michael Knyszek <[email protected]>
1 parent 3efa09f commit 2d8c199

File tree

2 files changed

+11
-2
lines changed

2 files changed

+11
-2
lines changed

src/runtime/proc.go

+2
Original file line numberDiff line numberDiff line change
@@ -2631,6 +2631,8 @@ func checkTimers(pp *p, now int64) (rnow, pollUntil int64, ran bool) {
26312631
rnow = nanotime()
26322632
}
26332633
for len(pp.timers) > 0 {
2634+
// Note that runtimer may temporarily unlock
2635+
// pp.timersLock.
26342636
if tw := runtimer(pp, rnow); tw != 0 {
26352637
if tw > 0 {
26362638
pollUntil = tw

src/runtime/time.go

+9-2
Original file line numberDiff line numberDiff line change
@@ -1011,6 +1011,8 @@ func nobarrierWakeTime(pp *p) int64 {
10111011
// Returns 0 if it ran a timer, -1 if there are no more timers, or the time
10121012
// when the first timer should run.
10131013
// The caller must have locked the timers for pp.
1014+
// If a timer is run, this will temporarily unlock the timers.
1015+
//go:systemstack
10141016
func runtimer(pp *p, now int64) int64 {
10151017
for {
10161018
t := pp.timers[0]
@@ -1027,6 +1029,8 @@ func runtimer(pp *p, now int64) int64 {
10271029
if !atomic.Cas(&t.status, s, timerRunning) {
10281030
continue
10291031
}
1032+
// Note that runOneTimer may temporarily unlock
1033+
// pp.timersLock.
10301034
runOneTimer(pp, t, now)
10311035
return 0
10321036

@@ -1081,6 +1085,8 @@ func runtimer(pp *p, now int64) int64 {
10811085

10821086
// runOneTimer runs a single timer.
10831087
// The caller must have locked the timers for pp.
1088+
// This will temporarily unlock the timers while running the timer function.
1089+
//go:systemstack
10841090
func runOneTimer(pp *p, t *timer, now int64) {
10851091
if raceenabled {
10861092
if pp.timerRaceCtx == 0 {
@@ -1122,11 +1128,12 @@ func runOneTimer(pp *p, t *timer, now int64) {
11221128
gp.racectx = pp.timerRaceCtx
11231129
}
11241130

1125-
// Note that since timers are locked here, f may not call
1126-
// addtimer or resettimer.
1131+
unlock(&pp.timersLock)
11271132

11281133
f(arg, seq)
11291134

1135+
lock(&pp.timersLock)
1136+
11301137
if raceenabled {
11311138
gp := getg()
11321139
gp.racectx = 0

0 commit comments

Comments
 (0)