diff options
author | Austin Clements <austin@google.com> | 2019-10-08 13:23:51 -0400 |
---|---|---|
committer | Austin Clements <austin@google.com> | 2019-11-02 21:51:18 +0000 |
commit | 62e53b79227dafc6afcd92240c89acb8c0e1dd56 (patch) | |
tree | 40e85fda03128d81c0146857f0456d9ea55c32f0 /src/runtime/preempt.go | |
parent | d16ec137568fb20e674a99c265e7c340c065dd69 (diff) | |
download | go-62e53b79227dafc6afcd92240c89acb8c0e1dd56.tar.gz go-62e53b79227dafc6afcd92240c89acb8c0e1dd56.zip |
runtime: use signals to preempt Gs for suspendG
This adds support for pausing a running G by sending a signal to its
M.
The main complication is that we want to target a G, but can only send
a signal to an M. Hence, the protocol we use is to simply mark the G
for preemption (which we already do) and send the M a "wake up and
look around" signal. The signal checks if it's running a G with a
preemption request and stops it if so in the same way that stack check
preemptions stop Gs. Since the preemption may fail (the G could be
moved or the signal could arrive at an unsafe point), we keep a count
of the number of received preemption signals. This lets stopG detect
if its request failed and should be retried without an explicit
channel back to suspendG.
For #10958, #24543.
Change-Id: I3e1538d5ea5200aeb434374abb5d5fdc56107e53
Reviewed-on: https://go-review.googlesource.com/c/go/+/201760
Run-TryBot: Austin Clements <austin@google.com>
Reviewed-by: Cherry Zhang <cherryyz@google.com>
Diffstat (limited to 'src/runtime/preempt.go')
-rw-r--r-- | src/runtime/preempt.go | 149 |
1 files changed, 146 insertions, 3 deletions
diff --git a/src/runtime/preempt.go b/src/runtime/preempt.go index 57ec493b8d..e1091cfd68 100644 --- a/src/runtime/preempt.go +++ b/src/runtime/preempt.go @@ -13,6 +13,11 @@ // 2. Synchronous safe-points occur when a running goroutine checks // for a preemption request. // +// 3. Asynchronous safe-points occur at any instruction in user code +// where the goroutine can be safely paused and a conservative +// stack and register scan can find stack roots. The runtime can +// stop a goroutine at an async safe-point using a signal. +// // At both blocked and synchronous safe-points, a goroutine's CPU // state is minimal and the garbage collector has complete information // about its entire stack. This makes it possible to deschedule a @@ -26,9 +31,32 @@ // to fail and enter the stack growth implementation, which will // detect that it was actually a preemption and redirect to preemption // handling. +// +// Preemption at asynchronous safe-points is implemented by suspending +// the thread using an OS mechanism (e.g., signals) and inspecting its +// state to determine if the goroutine was at an asynchronous +// safe-point. Since the thread suspension itself is generally +// asynchronous, it also checks if the running goroutine wants to be +// preempted, since this could have changed. If all conditions are +// satisfied, it adjusts the signal context to make it look like the +// signaled thread just called asyncPreempt and resumes the thread. +// asyncPreempt spills all registers and enters the scheduler. +// +// (An alternative would be to preempt in the signal handler itself. +// This would let the OS save and restore the register state and the +// runtime would only need to know how to extract potentially +// pointer-containing registers from the signal context. However, this +// would consume an M for every preempted G, and the scheduler itself +// is not designed to run from a signal handler, as it tends to +// allocate memory and start threads in the preemption path.) package runtime +import ( + "runtime/internal/atomic" + "runtime/internal/sys" +) + type suspendGState struct { g *g @@ -87,6 +115,8 @@ func suspendG(gp *g) suspendGState { // Drive the goroutine to a preemption point. stopped := false + var asyncM *m + var asyncGen uint32 for i := 0; ; i++ { switch s := readgstatus(gp); s { default: @@ -160,7 +190,7 @@ func suspendG(gp *g) suspendGState { case _Grunning: // Optimization: if there is already a pending preemption request // (from the previous loop iteration), don't bother with the atomics. - if gp.preemptStop && gp.preempt && gp.stackguard0 == stackPreempt { + if gp.preemptStop && gp.preempt && gp.stackguard0 == stackPreempt && asyncM == gp.m && atomic.Load(&asyncM.preemptGen) == asyncGen { break } @@ -174,7 +204,12 @@ func suspendG(gp *g) suspendGState { gp.preempt = true gp.stackguard0 = stackPreempt - // TODO: Inject asynchronous preemption. + // Send asynchronous preemption. + asyncM = gp.m + asyncGen = atomic.Load(&asyncM.preemptGen) + if preemptMSupported && debug.asyncpreemptoff == 0 { + preemptM(asyncM) + } casfrom_Gscanstatus(gp, _Gscanrunning, _Grunning) } @@ -245,5 +280,113 @@ func asyncPreempt() //go:nosplit func asyncPreempt2() { - // TODO: Enter scheduler + gp := getg() + gp.asyncSafePoint = true + mcall(preemptPark) + gp.asyncSafePoint = false +} + +// asyncPreemptStack is the bytes of stack space required to inject an +// asyncPreempt call. +var asyncPreemptStack = ^uintptr(0) + +func init() { + f := findfunc(funcPC(asyncPreempt)) + total := funcMaxSPDelta(f) + f = findfunc(funcPC(asyncPreempt2)) + total += funcMaxSPDelta(f) + // Add some overhead for return PCs, etc. + asyncPreemptStack = uintptr(total) + 8*sys.PtrSize + if asyncPreemptStack > _StackLimit { + // We need more than the nosplit limit. This isn't + // unsafe, but it may limit asynchronous preemption. + // + // This may be a problem if we start using more + // registers. In that case, we should store registers + // in a context object. If we pre-allocate one per P, + // asyncPreempt can spill just a few registers to the + // stack, then grab its context object and spill into + // it. When it enters the runtime, it would allocate a + // new context for the P. + print("runtime: asyncPreemptStack=", asyncPreemptStack, "\n") + throw("async stack too large") + } +} + +// wantAsyncPreempt returns whether an asynchronous preemption is +// queued for gp. +func wantAsyncPreempt(gp *g) bool { + return gp.preemptStop && readgstatus(gp)&^_Gscan == _Grunning +} + +// isAsyncSafePoint reports whether gp at instruction PC is an +// asynchronous safe point. This indicates that: +// +// 1. It's safe to suspend gp and conservatively scan its stack and +// registers. There are no potentially hidden pointer values and it's +// not in the middle of an atomic sequence like a write barrier. +// +// 2. gp has enough stack space to inject the asyncPreempt call. +// +// 3. It's generally safe to interact with the runtime, even if we're +// in a signal handler stopped here. For example, there are no runtime +// locks held, so acquiring a runtime lock won't self-deadlock. +func isAsyncSafePoint(gp *g, pc, sp uintptr) bool { + mp := gp.m + + // Only user Gs can have safe-points. We check this first + // because it's extremely common that we'll catch mp in the + // scheduler processing this G preemption. + if mp.curg != gp { + return false + } + + // Check M state. + if mp.p == 0 || !canPreemptM(mp) { + return false + } + + // Check stack space. + if sp < gp.stack.lo || sp-gp.stack.lo < asyncPreemptStack { + return false + } + + // Check if PC is an unsafe-point. + f := findfunc(pc) + if !f.valid() { + // Not Go code. + return false + } + smi := pcdatavalue(f, _PCDATA_StackMapIndex, pc, nil) + if smi == -2 { + // Unsafe-point marked by compiler. This includes + // atomic sequences (e.g., write barrier) and nosplit + // functions (except at calls). + return false + } + if funcdata(f, _FUNCDATA_LocalsPointerMaps) == nil { + // This is assembly code. Don't assume it's + // well-formed. + // + // TODO: Are there cases that are safe but don't have a + // locals pointer map, like empty frame functions? + return false + } + if hasPrefix(funcname(f), "runtime.") || + hasPrefix(funcname(f), "runtime/internal/") || + hasPrefix(funcname(f), "reflect.") { + // For now we never async preempt the runtime or + // anything closely tied to the runtime. Known issues + // include: various points in the scheduler ("don't + // preempt between here and here"), much of the defer + // implementation (untyped info on stack), bulk write + // barriers (write barrier check), + // reflect.{makeFuncStub,methodValueCall}. + // + // TODO(austin): We should improve this, or opt things + // in incrementally. + return false + } + + return true } |