Skip to content

Commit 13b18ec

Browse files
committed
runtime: move scheduling decisions by schedule into findrunnable
This change moves several scheduling decisions made by schedule into findrunnable. The main motivation behind this change is the fact that stopped Ms can't become dedicated or fractional GC workers. The main reason for this is that when a stopped M wakes up, it stays in findrunnable until it finds work, which means it will never consider GC work. On that note, it'll also never consider becoming the trace reader, either. Another way of looking at it is that this change tries to make findrunnable aware of more sources of work than it was before. With this change, any M in findrunnable should be capable of becoming a GC worker, resolving #44313. While we're here, let's also make more sources of work, such as the trace reader, visible to handoffp, which should really be checking all sources of work. With that, we also now correctly handle the case where StopTrace is called from the last live M that is also locked (#39004). stoplockedm calls handoffp to start a new M and handle the work it cannot, and once we include the trace reader in that, we ensure that the trace reader gets scheduled. This change attempts to preserve the exact same ordering of work checking to reduce its impact. One consequence of this change is that upon entering schedule, some sources of work won't be checked twice (i.e. the local and global runqs, and timers) as they do now, which in some sense gives them a lower priority than they had before. Fixes #39004. Fixes #44313. Change-Id: I5d8b7f63839db8d9a3e47cdda604baac1fe615ce Reviewed-on: https://go-review.googlesource.com/c/go/+/393880 Reviewed-by: Michael Pratt <[email protected]> Run-TryBot: Michael Knyszek <[email protected]> TryBot-Result: Gopher Robot <[email protected]>
1 parent e1b5f34 commit 13b18ec

File tree

2 files changed

+64
-62
lines changed

2 files changed

+64
-62
lines changed

src/runtime/proc.go

+54-60
Original file line numberDiff line numberDiff line change
@@ -2351,6 +2351,11 @@ func handoffp(_p_ *p) {
23512351
startm(_p_, false)
23522352
return
23532353
}
2354+
// if there's trace work to do, start it straight away
2355+
if (trace.enabled || trace.shutdown) && traceReaderAvailable() {
2356+
startm(_p_, false)
2357+
return
2358+
}
23542359
// if it has GC work, start it straight away
23552360
if gcBlackenEnabled != 0 && gcMarkWorkAvailable(_p_) {
23562361
startm(_p_, false)
@@ -2535,7 +2540,9 @@ func execute(gp *g, inheritTime bool) {
25352540

25362541
// Finds a runnable goroutine to execute.
25372542
// Tries to steal from other P's, get g from local or global queue, poll network.
2538-
func findrunnable() (gp *g, inheritTime bool) {
2543+
// tryWakeP indicates that the returned goroutine is not normal (GC worker, trace
2544+
// reader) so the caller should try to wake a P.
2545+
func findRunnable() (gp *g, inheritTime, tryWakeP bool) {
25392546
_g_ := getg()
25402547

25412548
// The conditions here and in handoffp must agree: if
@@ -2552,8 +2559,43 @@ top:
25522559
runSafePointFn()
25532560
}
25542561

2562+
// now and pollUntil are saved for work stealing later,
2563+
// which may steal timers. It's important that between now
2564+
// and then, nothing blocks, so these numbers remain mostly
2565+
// relevant.
25552566
now, pollUntil, _ := checkTimers(_p_, 0)
25562567

2568+
// Try to schedule the trace reader.
2569+
if trace.enabled || trace.shutdown {
2570+
gp = traceReader()
2571+
if gp != nil {
2572+
casgstatus(gp, _Gwaiting, _Grunnable)
2573+
traceGoUnpark(gp, 0)
2574+
return gp, false, true
2575+
}
2576+
}
2577+
2578+
// Try to schedule a GC worker.
2579+
if gcBlackenEnabled != 0 {
2580+
gp = gcController.findRunnableGCWorker(_p_)
2581+
if gp != nil {
2582+
return gp, false, true
2583+
}
2584+
}
2585+
2586+
// Check the global runnable queue once in a while to ensure fairness.
2587+
// Otherwise two goroutines can completely occupy the local runqueue
2588+
// by constantly respawning each other.
2589+
if _p_.schedtick%61 == 0 && sched.runqsize > 0 {
2590+
lock(&sched.lock)
2591+
gp = globrunqget(_p_, 1)
2592+
unlock(&sched.lock)
2593+
if gp != nil {
2594+
return gp, false, false
2595+
}
2596+
}
2597+
2598+
// Wake up the finalizer G.
25572599
if fingwait && fingwake {
25582600
if gp := wakefing(); gp != nil {
25592601
ready(gp, 0, true)
@@ -2565,7 +2607,7 @@ top:
25652607

25662608
// local runq
25672609
if gp, inheritTime := runqget(_p_); gp != nil {
2568-
return gp, inheritTime
2610+
return gp, inheritTime, false
25692611
}
25702612

25712613
// global runq
@@ -2574,7 +2616,7 @@ top:
25742616
gp := globrunqget(_p_, 0)
25752617
unlock(&sched.lock)
25762618
if gp != nil {
2577-
return gp, false
2619+
return gp, false, false
25782620
}
25792621
}
25802622

@@ -2593,7 +2635,7 @@ top:
25932635
if trace.enabled {
25942636
traceGoUnpark(gp, 0)
25952637
}
2596-
return gp, false
2638+
return gp, false, false
25972639
}
25982640
}
25992641

@@ -2613,7 +2655,7 @@ top:
26132655
now = tnow
26142656
if gp != nil {
26152657
// Successfully stole.
2616-
return gp, inheritTime
2658+
return gp, inheritTime, false
26172659
}
26182660
if newWork {
26192661
// There may be new timer or GC work; restart to
@@ -2639,7 +2681,7 @@ top:
26392681
if trace.enabled {
26402682
traceGoUnpark(gp, 0)
26412683
}
2642-
return gp, false
2684+
return gp, false, false
26432685
}
26442686
gcController.removeIdleMarkWorker()
26452687
}
@@ -2654,7 +2696,7 @@ top:
26542696
if trace.enabled {
26552697
traceGoUnpark(gp, 0)
26562698
}
2657-
return gp, false
2699+
return gp, false, false
26582700
}
26592701
if otherReady {
26602702
goto top
@@ -2679,7 +2721,7 @@ top:
26792721
if sched.runqsize != 0 {
26802722
gp := globrunqget(_p_, 0)
26812723
unlock(&sched.lock)
2682-
return gp, false
2724+
return gp, false, false
26832725
}
26842726
if releasep() != _p_ {
26852727
throw("findrunnable: wrong p")
@@ -2742,7 +2784,7 @@ top:
27422784
if trace.enabled {
27432785
traceGoUnpark(gp, 0)
27442786
}
2745-
return gp, false
2787+
return gp, false, false
27462788
}
27472789

27482790
// Finally, check for timer creation or expiry concurrently with
@@ -2800,7 +2842,7 @@ top:
28002842
if trace.enabled {
28012843
traceGoUnpark(gp, 0)
28022844
}
2803-
return gp, false
2845+
return gp, false, false
28042846
}
28052847
if wasSpinning {
28062848
_g_.m.spinning = true
@@ -3147,62 +3189,14 @@ top:
31473189
pp := _g_.m.p.ptr()
31483190
pp.preempt = false
31493191

3150-
if sched.gcwaiting != 0 {
3151-
gcstopm()
3152-
goto top
3153-
}
3154-
if pp.runSafePointFn != 0 {
3155-
runSafePointFn()
3156-
}
3157-
3158-
// Sanity check: if we are spinning, the run queue should be empty.
3192+
// Safety check: if we are spinning, the run queue should be empty.
31593193
// Check this before calling checkTimers, as that might call
31603194
// goready to put a ready goroutine on the local run queue.
31613195
if _g_.m.spinning && (pp.runnext != 0 || pp.runqhead != pp.runqtail) {
31623196
throw("schedule: spinning with local work")
31633197
}
31643198

3165-
checkTimers(pp, 0)
3166-
3167-
var gp *g
3168-
var inheritTime bool
3169-
3170-
// Normal goroutines will check for need to wakeP in ready,
3171-
// but GCworkers and tracereaders will not, so the check must
3172-
// be done here instead.
3173-
tryWakeP := false
3174-
if trace.enabled || trace.shutdown {
3175-
gp = traceReader()
3176-
if gp != nil {
3177-
casgstatus(gp, _Gwaiting, _Grunnable)
3178-
traceGoUnpark(gp, 0)
3179-
tryWakeP = true
3180-
}
3181-
}
3182-
if gp == nil && gcBlackenEnabled != 0 {
3183-
gp = gcController.findRunnableGCWorker(_g_.m.p.ptr())
3184-
if gp != nil {
3185-
tryWakeP = true
3186-
}
3187-
}
3188-
if gp == nil {
3189-
// Check the global runnable queue once in a while to ensure fairness.
3190-
// Otherwise two goroutines can completely occupy the local runqueue
3191-
// by constantly respawning each other.
3192-
if _g_.m.p.ptr().schedtick%61 == 0 && sched.runqsize > 0 {
3193-
lock(&sched.lock)
3194-
gp = globrunqget(_g_.m.p.ptr(), 1)
3195-
unlock(&sched.lock)
3196-
}
3197-
}
3198-
if gp == nil {
3199-
gp, inheritTime = runqget(_g_.m.p.ptr())
3200-
// We can see gp != nil here even if the M is spinning,
3201-
// if checkTimers added a local goroutine via goready.
3202-
}
3203-
if gp == nil {
3204-
gp, inheritTime = findrunnable() // blocks until work is available
3205-
}
3199+
gp, inheritTime, tryWakeP := findRunnable() // blocks until work is available
32063200

32073201
// This thread is going to run a goroutine and is not spinning anymore,
32083202
// so if it was marked as spinning we need to reset it now and potentially

src/runtime/trace.go

+10-2
Original file line numberDiff line numberDiff line change
@@ -461,12 +461,13 @@ func ReadTrace() []byte {
461461
}
462462

463463
// traceReader returns the trace reader that should be woken up, if any.
464+
// Callers should first check that trace.enabled or trace.shutdown is set.
464465
func traceReader() *g {
465-
if trace.reader == 0 || (trace.fullHead == 0 && !trace.shutdown) {
466+
if !traceReaderAvailable() {
466467
return nil
467468
}
468469
lock(&trace.lock)
469-
if trace.reader == 0 || (trace.fullHead == 0 && !trace.shutdown) {
470+
if !traceReaderAvailable() {
470471
unlock(&trace.lock)
471472
return nil
472473
}
@@ -476,6 +477,13 @@ func traceReader() *g {
476477
return gp
477478
}
478479

480+
// traceReaderAvailable returns true if the trace reader is not currently
481+
// scheduled and should be. Callers should first check that trace.enabled
482+
// or trace.shutdown is set.
483+
func traceReaderAvailable() bool {
484+
return trace.reader != 0 && (trace.fullHead != 0 || trace.shutdown)
485+
}
486+
479487
// traceProcFree frees trace buffer associated with pp.
480488
func traceProcFree(pp *p) {
481489
buf := pp.tracebuf

0 commit comments

Comments
 (0)