diff options
author | Michael Pratt <mpratt@google.com> | 2022-06-08 15:59:37 -0400 |
---|---|---|
committer | Gopher Robot <gobot@golang.org> | 2022-06-08 21:56:02 +0000 |
commit | 13f6be28338c43d3aa22a4467b34a45c40f83593 (patch) | |
tree | 468ce59ad62759e2185ef1d428cabd3f8c1ee380 | |
parent | 1292176bc98be4b7b9d24abec05e88b3dbd89e21 (diff) | |
download | go-13f6be28338c43d3aa22a4467b34a45c40f83593.tar.gz go-13f6be28338c43d3aa22a4467b34a45c40f83593.zip |
runtime: use pidleget for faketime jump
In faketime mode, checkdead is responsible for jumping time forward to
the next timer expiration, and waking an M to handle the newly ready
timer.
Currently it pulls the exact P that owns the next timer off of the pidle
list. In theory this is efficient because that P is immediately eligible
to run the timer without stealing. Unfortunately it is also fraught with
peril because we are skipping all of the bookkeeping in pidleget:
* Skipped updates to timerpMask mean that our timers may not be eligible
for stealing, as they should be.
* Skipped updates to idlepMask mean that our runq may not be eligible
for stealing, as they should be.
* Skipped updates to sched.npidle may break tracking of spinning Ms,
potentially resulting in lost work.
* Finally, as of CL 410122, skipped updates to p.limiterEvent may affect
the GC limiter, or cause a fatal throw when another event occurs.
The last case has finally undercovered this issue since it quickly
results in a hard crash.
We could add all of these updates into checkdead, but it is much more
maintainable to keep this logic in one place and use pidleget here like
everywhere else in the runtime. This means we probably won't wake the
P owning the timer, meaning that the P will need to steal the timer,
which is less efficient, but faketime is not a performance-sensitive
build mode. Note that the M will automatically make itself a spinning M
to make it eligible to steal since it is the only one running.
Fixes #53294
For #52890
Change-Id: I4acc3d259b9b4d7dc02608581c8b4fd259f272e9
Reviewed-on: https://go-review.googlesource.com/c/go/+/411119
Run-TryBot: Michael Pratt <mpratt@google.com>
Auto-Submit: Michael Pratt <mpratt@google.com>
TryBot-Result: Gopher Robot <gobot@golang.org>
Reviewed-by: Michael Knyszek <mknyszek@google.com>
-rw-r--r-- | src/runtime/proc.go | 26 | ||||
-rw-r--r-- | src/runtime/time.go | 11 |
2 files changed, 20 insertions, 17 deletions
diff --git a/src/runtime/proc.go b/src/runtime/proc.go index dc2957b939..3991a48b10 100644 --- a/src/runtime/proc.go +++ b/src/runtime/proc.go @@ -5071,14 +5071,15 @@ func checkdead() { // Maybe jump time forward for playground. if faketime != 0 { - when, _p_ := timeSleepUntil() - if _p_ != nil { + if when := timeSleepUntil(); when < maxWhen { faketime = when - for pp := &sched.pidle; *pp != 0; pp = &(*pp).ptr().link { - if (*pp).ptr() == _p_ { - *pp = _p_.link - break - } + + // Start an M to steal the timer. + pp, _ := pidleget(faketime) + if pp == nil { + // There should always be a free P since + // nothing is running. + throw("checkdead: no p for timer") } mp := mget() if mp == nil { @@ -5086,7 +5087,12 @@ func checkdead() { // nothing is running. throw("checkdead: no m for timer") } - mp.nextp.set(_p_) + // M must be spinning to steal. We set this to be + // explicit, but since this is the only M it would + // become spinning on its own anyways. + atomic.Xadd(&sched.nmspinning, 1) + mp.spinning = true + mp.nextp.set(pp) notewakeup(&mp.park) return } @@ -5158,7 +5164,7 @@ func sysmon() { lock(&sched.lock) if atomic.Load(&sched.gcwaiting) != 0 || atomic.Load(&sched.npidle) == uint32(gomaxprocs) { syscallWake := false - next, _ := timeSleepUntil() + next := timeSleepUntil() if next > now { atomic.Store(&sched.sysmonwait, 1) unlock(&sched.lock) @@ -5231,7 +5237,7 @@ func sysmon() { // // See issue 42515 and // https://gnats.netbsd.org/cgi-bin/query-pr-single.pl?number=50094. - if next, _ := timeSleepUntil(); next < now { + if next := timeSleepUntil(); next < now { startm(nil, false) } } diff --git a/src/runtime/time.go b/src/runtime/time.go index e4d8269987..aec39083b4 100644 --- a/src/runtime/time.go +++ b/src/runtime/time.go @@ -1016,12 +1016,11 @@ func updateTimerModifiedEarliest(pp *p, nextwhen int64) { } } -// timeSleepUntil returns the time when the next timer should fire, -// and the P that holds the timer heap that that timer is on. +// timeSleepUntil returns the time when the next timer should fire. Returns +// maxWhen if there are no timers. // This is only called by sysmon and checkdead. -func timeSleepUntil() (int64, *p) { +func timeSleepUntil() int64 { next := int64(maxWhen) - var pret *p // Prevent allp slice changes. This is like retake. lock(&allpLock) @@ -1035,18 +1034,16 @@ func timeSleepUntil() (int64, *p) { w := int64(atomic.Load64(&pp.timer0When)) if w != 0 && w < next { next = w - pret = pp } w = int64(atomic.Load64(&pp.timerModifiedEarliest)) if w != 0 && w < next { next = w - pret = pp } } unlock(&allpLock) - return next, pret + return next } // Heap maintenance algorithms. |