aboutsummaryrefslogtreecommitdiff
path: root/src/runtime/preempt.go
diff options
context:
space:
mode:
authorAustin Clements <austin@google.com>2019-10-08 13:23:51 -0400
committerAustin Clements <austin@google.com>2019-11-02 21:51:18 +0000
commit62e53b79227dafc6afcd92240c89acb8c0e1dd56 (patch)
tree40e85fda03128d81c0146857f0456d9ea55c32f0 /src/runtime/preempt.go
parentd16ec137568fb20e674a99c265e7c340c065dd69 (diff)
downloadgo-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.go149
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
}