Skip to content

Commit 8a6cd7a

Browse files
mknyszekandybons
authored andcommitted
[release-branch.go1.13] runtime: fix lock acquire cycles related to scavenge.lock
There are currently two edges in the lock cycle graph caused by scavenge.lock: with sched.lock and mheap_.lock. These edges appear because of the call to ready() and stack growths respectively. Furthermore, there's already an invariant in the code wherein mheap_.lock must be acquired before scavenge.lock, hence the cycle. The fix to this is to bring scavenge.lock higher in the lock cycle graph, such that sched.lock and mheap_.lock are only acquired once scavenge.lock is already held. To faciliate this change, we move scavenger waking outside of gcSetTriggerRatio such that it doesn't have to happen with the heap locked. Furthermore, we check scavenge generation numbers with the heap locked by using gopark instead of goparkunlock, and specify a function which aborts the park should there be any skew in generation count. Fixes #34150. Change-Id: I3519119214bac66375e2b1262b36ce376c820d12 Reviewed-on: https://go-review.googlesource.com/c/go/+/191977 Run-TryBot: Michael Knyszek <[email protected]> TryBot-Result: Gobot Gobot <[email protected]> Reviewed-by: Keith Randall <[email protected]> (cherry picked from commit 62e4156) Reviewed-on: https://go-review.googlesource.com/c/go/+/197501 Reviewed-by: Austin Clements <[email protected]>
1 parent 8c8a881 commit 8a6cd7a

File tree

3 files changed

+45
-29
lines changed

3 files changed

+45
-29
lines changed

src/runtime/mgc.go

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -229,6 +229,8 @@ func setGCPercent(in int32) (out int32) {
229229
gcSetTriggerRatio(memstats.triggerRatio)
230230
unlock(&mheap_.lock)
231231
})
232+
// Pacing changed, so the scavenger should be awoken.
233+
wakeScavenger()
232234

233235
// If we just disabled GC, wait for any concurrent GC mark to
234236
// finish so we always return with no GC running.
@@ -1652,6 +1654,9 @@ func gcMarkTermination(nextTriggerRatio float64) {
16521654
// Update GC trigger and pacing for the next cycle.
16531655
gcSetTriggerRatio(nextTriggerRatio)
16541656

1657+
// Pacing changed, so the scavenger should be awoken.
1658+
wakeScavenger()
1659+
16551660
// Update timing memstats
16561661
now := nanotime()
16571662
sec, nsec, _ := time_now()

src/runtime/mgcscavenge.go

Lines changed: 39 additions & 29 deletions
Original file line numberDiff line numberDiff line change
@@ -154,36 +154,41 @@ func gcPaceScavenger() {
154154

155155
now := nanotime()
156156

157-
lock(&scavenge.lock)
158-
159157
// Update all the pacing parameters in mheap with scavenge.lock held,
160158
// so that scavenge.gen is kept in sync with the updated values.
161159
mheap_.scavengeRetainedGoal = retainedGoal
162160
mheap_.scavengeRetainedBasis = retainedNow
163161
mheap_.scavengeTimeBasis = now
164162
mheap_.scavengeBytesPerNS = float64(totalWork) / float64(totalTime)
165-
scavenge.gen++ // increase scavenge generation
166-
167-
// Wake up background scavenger if needed, since the pacing was just updated.
168-
wakeScavengerLocked()
169-
170-
unlock(&scavenge.lock)
163+
mheap_.scavengeGen++ // increase scavenge generation
171164
}
172165

173-
// State of the background scavenger.
166+
// Sleep/wait state of the background scavenger.
174167
var scavenge struct {
175168
lock mutex
176169
g *g
177170
parked bool
178171
timer *timer
179-
gen uint32 // read with either lock or mheap_.lock, write with both
172+
173+
// Generation counter.
174+
//
175+
// It represents the last generation count (as defined by
176+
// mheap_.scavengeGen) checked by the scavenger and is updated
177+
// each time the scavenger checks whether it is on-pace.
178+
//
179+
// Skew between this field and mheap_.scavengeGen is used to
180+
// determine whether a new update is available.
181+
//
182+
// Protected by mheap_.lock.
183+
gen uint64
180184
}
181185

182-
// wakeScavengerLocked unparks the scavenger if necessary. It must be called
186+
// wakeScavenger unparks the scavenger if necessary. It must be called
183187
// after any pacing update.
184188
//
185-
// scavenge.lock must be held.
186-
func wakeScavengerLocked() {
189+
// mheap_.lock and scavenge.lock must not be held.
190+
func wakeScavenger() {
191+
lock(&scavenge.lock)
187192
if scavenge.parked {
188193
// Try to stop the timer but we don't really care if we succeed.
189194
// It's possible that either a timer was never started, or that
@@ -198,36 +203,43 @@ func wakeScavengerLocked() {
198203
scavenge.parked = false
199204
ready(scavenge.g, 0, true)
200205
}
206+
unlock(&scavenge.lock)
201207
}
202208

203209
// scavengeSleep attempts to put the scavenger to sleep for ns.
204-
// It also checks to see if gen != scavenge.gen before going to sleep,
205-
// and aborts if true (meaning an update had occurred).
206210
//
207211
// Note that this function should only be called by the scavenger.
208212
//
209213
// The scavenger may be woken up earlier by a pacing change, and it may not go
210214
// to sleep at all if there's a pending pacing change.
211215
//
212216
// Returns false if awoken early (i.e. true means a complete sleep).
213-
func scavengeSleep(gen uint32, ns int64) bool {
217+
func scavengeSleep(ns int64) bool {
214218
lock(&scavenge.lock)
215219

216-
// If there was an update, just abort the sleep.
217-
if scavenge.gen != gen {
220+
// First check if there's a pending update.
221+
// If there is one, don't bother sleeping.
222+
var hasUpdate bool
223+
systemstack(func() {
224+
lock(&mheap_.lock)
225+
hasUpdate = mheap_.scavengeGen != scavenge.gen
226+
unlock(&mheap_.lock)
227+
})
228+
if hasUpdate {
218229
unlock(&scavenge.lock)
219230
return false
220231
}
221232

222233
// Set the timer.
234+
//
235+
// This must happen here instead of inside gopark
236+
// because we can't close over any variables without
237+
// failing escape analysis.
223238
now := nanotime()
224239
scavenge.timer.when = now + ns
225240
startTimer(scavenge.timer)
226241

227-
// Park the goroutine. It's fine that we don't publish the
228-
// fact that the timer was set; even if the timer wakes up
229-
// and fire scavengeReady before we park, it'll block on
230-
// scavenge.lock.
242+
// Mark ourself as asleep and go to sleep.
231243
scavenge.parked = true
232244
goparkunlock(&scavenge.lock, waitReasonSleep, traceEvGoSleep, 2)
233245

@@ -248,9 +260,7 @@ func bgscavenge(c chan int) {
248260

249261
scavenge.timer = new(timer)
250262
scavenge.timer.f = func(_ interface{}, _ uintptr) {
251-
lock(&scavenge.lock)
252-
wakeScavengerLocked()
253-
unlock(&scavenge.lock)
263+
wakeScavenger()
254264
}
255265

256266
c <- 1
@@ -279,14 +289,14 @@ func bgscavenge(c chan int) {
279289
released := uintptr(0)
280290
park := false
281291
ttnext := int64(0)
282-
gen := uint32(0)
283292

284293
// Run on the system stack since we grab the heap lock,
285294
// and a stack growth with the heap lock means a deadlock.
286295
systemstack(func() {
287296
lock(&mheap_.lock)
288297

289-
gen = scavenge.gen
298+
// Update the last generation count that the scavenger has handled.
299+
scavenge.gen = mheap_.scavengeGen
290300

291301
// If background scavenging is disabled or if there's no work to do just park.
292302
retained := heapRetained()
@@ -343,7 +353,7 @@ func bgscavenge(c chan int) {
343353
if released == 0 {
344354
// If we were unable to release anything this may be because there's
345355
// no free memory available to scavenge. Go to sleep and try again.
346-
if scavengeSleep(gen, retryDelayNS) {
356+
if scavengeSleep(retryDelayNS) {
347357
// If we successfully slept through the delay, back off exponentially.
348358
retryDelayNS *= 2
349359
}
@@ -355,7 +365,7 @@ func bgscavenge(c chan int) {
355365
// If there's an appreciable amount of time until the next scavenging
356366
// goal, just sleep. We'll get woken up if anything changes and this
357367
// way we avoid spinning.
358-
scavengeSleep(gen, ttnext)
368+
scavengeSleep(ttnext)
359369
continue
360370
}
361371

src/runtime/mheap.go

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -107,6 +107,7 @@ type mheap struct {
107107
scavengeRetainedBasis uint64
108108
scavengeBytesPerNS float64
109109
scavengeRetainedGoal uint64
110+
scavengeGen uint64 // incremented on each pacing update
110111

111112
// Page reclaimer state
112113

0 commit comments

Comments
 (0)