Skip to content

Commit d8cf224

Browse files
committed
runtime: disable idle mark workers with at least one dedicated worker
This change completes the proposal laid out in #44163. With #44313 resolved, we now ensure that stopped Ms are able to wake up and become dedicated GC workers. As a result, idle GC workers are in theory no longer required to be a proxy for scheduling dedicated mark workers. And, with at least one dedicated mark worker running (which is non-preemptible) we ensure the GC makes progress in all circumstances when at least one is running. Currently we ensure at least one idle mark worker is available at all times because it's possible before #44313 that a dedicated worker doesn't ever get scheduled, leading to a deadlock if user goroutines block on a GC completing. But now that extra idle mark worker should be unnecessary to ensure GC progress when at least one dedicated mark worker is going to be scheduled. Fixes #44163. Change-Id: I62889ef2db4e69d44da883e8e6eebcfe5398c86d Reviewed-on: https://go-review.googlesource.com/c/go/+/395634 Reviewed-by: Michael Pratt <[email protected]> Run-TryBot: Michael Knyszek <[email protected]> TryBot-Result: Gopher Robot <[email protected]>
1 parent 13b18ec commit d8cf224

File tree

1 file changed

+30
-17
lines changed

1 file changed

+30
-17
lines changed

src/runtime/mgcpacer.go

Lines changed: 30 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -287,7 +287,7 @@ type gcControllerState struct {
287287
//
288288
// The top int32 is the maximum number of idle mark workers allowed to
289289
// execute concurrently. Normally, this number is just gomaxprocs. However,
290-
// during periodic GC cycles it is set to 1 because the system is idle
290+
// during periodic GC cycles it is set to 0 because the system is idle
291291
// anyway; there's no need to go full blast on all of GOMAXPROCS.
292292
//
293293
// The maximum number of idle mark workers is used to prevent new workers
@@ -296,17 +296,22 @@ type gcControllerState struct {
296296
// transiently exceed the maximum. This could happen if the maximum changes
297297
// just after a GC ends, and an M with no P.
298298
//
299-
// Note that the maximum may not be zero because idle-priority mark workers
300-
// are vital to GC progress. Consider a situation in which goroutines
301-
// block on the GC (such as via runtime.GOMAXPROCS) and only fractional
302-
// mark workers are scheduled (e.g. GOMAXPROCS=1). Without idle-priority
303-
// mark workers, the last running M might skip scheduling a fractional
304-
// mark worker if its utilization goal is met, such that once it goes to
305-
// sleep (because there's nothing to do), there will be nothing else to
306-
// spin up a new M for the fractional worker in the future, stalling GC
307-
// progress and causing a deadlock. However, idle-priority workers will
308-
// *always* run when there is nothing left to do, ensuring the GC makes
309-
// progress.
299+
// Note that if we have no dedicated mark workers, we set this value to
300+
// 1 in this case we only have fractional GC workers which aren't scheduled
301+
// strictly enough to ensure GC progress. As a result, idle-priority mark
302+
// workers are vital to GC progress in these situations.
303+
//
304+
// For example, consider a situation in which goroutines block on the GC
305+
// (such as via runtime.GOMAXPROCS) and only fractional mark workers are
306+
// scheduled (e.g. GOMAXPROCS=1). Without idle-priority mark workers, the
307+
// last running M might skip scheduling a fractional mark worker if its
308+
// utilization goal is met, such that once it goes to sleep (because there's
309+
// nothing to do), there will be nothing else to spin up a new M for the
310+
// fractional worker in the future, stalling GC progress and causing a
311+
// deadlock. However, idle-priority workers will *always* run when there is
312+
// nothing left to do, ensuring the GC makes progress.
313+
//
314+
// See github.com/golang/go/issues/44163 for more details.
310315
idleMarkWorkers atomic.Uint64
311316

312317
// assistWorkPerByte is the ratio of scan work to allocated
@@ -430,11 +435,19 @@ func (c *gcControllerState) startCycle(markStartTime int64, procs int, trigger g
430435
}
431436

432437
if trigger.kind == gcTriggerTime {
433-
// During a periodic GC cycle, avoid having more than
434-
// one idle mark worker running at a time. We need to have
435-
// at least one to ensure the GC makes progress, but more than
436-
// one is unnecessary.
437-
c.setMaxIdleMarkWorkers(1)
438+
// During a periodic GC cycle, reduce the number of idle mark workers
439+
// required. However, we need at least one dedicated mark worker or
440+
// idle GC worker to ensure GC progress in some scenarios (see comment
441+
// on maxIdleMarkWorkers).
442+
if c.dedicatedMarkWorkersNeeded > 0 {
443+
c.setMaxIdleMarkWorkers(0)
444+
} else {
445+
// TODO(mknyszek): The fundamental reason why we need this is because
446+
// we can't count on the fractional mark worker to get scheduled.
447+
// Fix that by ensuring it gets scheduled according to its quota even
448+
// if the rest of the application is idle.
449+
c.setMaxIdleMarkWorkers(1)
450+
}
438451
} else {
439452
// N.B. gomaxprocs and dedicatedMarkWorkersNeeded is guaranteed not to
440453
// change during a GC cycle.

0 commit comments

Comments
 (0)