diff options
author | Austin Clements <austin@google.com> | 2019-10-25 16:17:41 -0400 |
---|---|---|
committer | Austin Clements <austin@google.com> | 2019-11-20 17:13:59 +0000 |
commit | b89b4623eb70cbdc6b0aea43a5a826b7a26f20a7 (patch) | |
tree | dbe03bd384e2b2cf0ce13dd773a8c269c08612f7 /src/runtime/preempt.go | |
parent | 6fd467ee29226bf4b875921b7cb3b692c9db52ef (diff) | |
download | go-b89b4623eb70cbdc6b0aea43a5a826b7a26f20a7.tar.gz go-b89b4623eb70cbdc6b0aea43a5a826b7a26f20a7.zip |
runtime: support preemption on windows/{386,amd64}
This implements preemptM on Windows using SuspendThead and
ResumeThread.
Unlike on POSIX platforms, preemptM on Windows happens synchronously.
This means we need a make a few other tweaks to suspendG:
1. We need to CAS the G back to _Grunning before doing the preemptM,
or there's a good chance we'll just catch the G spinning on its
status in the runtime, which won't be preemptible.
2. We need to rate-limit preemptM attempts. Otherwise, if the first
attempt catches the G at a non-preemptible point, the busy loop in
suspendG may hammer it so hard that it never makes it past that
non-preemptible point.
Updates #10958, #24543.
Change-Id: Ie53b098811096f7e45d864afd292dc9e999ce226
Reviewed-on: https://go-review.googlesource.com/c/go/+/204340
Run-TryBot: Austin Clements <austin@google.com>
TryBot-Result: Gobot Gobot <gobot@golang.org>
Reviewed-by: Cherry Zhang <cherryyz@google.com>
Diffstat (limited to 'src/runtime/preempt.go')
-rw-r--r-- | src/runtime/preempt.go | 31 |
1 files changed, 25 insertions, 6 deletions
diff --git a/src/runtime/preempt.go b/src/runtime/preempt.go index f154614913..60e1bcef5f 100644 --- a/src/runtime/preempt.go +++ b/src/runtime/preempt.go @@ -118,6 +118,7 @@ func suspendG(gp *g) suspendGState { stopped := false var asyncM *m var asyncGen uint32 + var nextPreemptM int64 for i := 0; ; i++ { switch s := readgstatus(gp); s { default: @@ -205,14 +206,32 @@ func suspendG(gp *g) suspendGState { gp.preempt = true gp.stackguard0 = stackPreempt - // Send asynchronous preemption. - asyncM = gp.m - asyncGen = atomic.Load(&asyncM.preemptGen) - if preemptMSupported && debug.asyncpreemptoff == 0 { - preemptM(asyncM) - } + // Prepare for asynchronous preemption. + asyncM2 := gp.m + asyncGen2 := atomic.Load(&asyncM2.preemptGen) + needAsync := asyncM != asyncM2 || asyncGen != asyncGen2 + asyncM = asyncM2 + asyncGen = asyncGen2 casfrom_Gscanstatus(gp, _Gscanrunning, _Grunning) + + // Send asynchronous preemption. We do this + // after CASing the G back to _Grunning + // because preemptM may be synchronous and we + // don't want to catch the G just spinning on + // its status. + if preemptMSupported && debug.asyncpreemptoff == 0 && needAsync { + // Rate limit preemptM calls. This is + // particularly important on Windows + // where preemptM is actually + // synchronous and the spin loop here + // can lead to live-lock. + now := nanotime() + if now >= nextPreemptM { + nextPreemptM = now + yieldDelay/2 + preemptM(asyncM) + } + } } // TODO: Don't busy wait. This loop should really only |