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/signal_unix.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/signal_unix.go')
-rw-r--r-- | src/runtime/signal_unix.go | 70 |
1 files changed, 70 insertions, 0 deletions
diff --git a/src/runtime/signal_unix.go b/src/runtime/signal_unix.go index e0757acbed..5e4361e7a1 100644 --- a/src/runtime/signal_unix.go +++ b/src/runtime/signal_unix.go @@ -38,6 +38,38 @@ const ( _SIG_IGN uintptr = 1 ) +// sigPreempt is the signal used for non-cooperative preemption. +// +// There's no good way to choose this signal, but there are some +// heuristics: +// +// 1. It should be a signal that's passed-through by debuggers by +// default. On Linux, this is SIGALRM, SIGURG, SIGCHLD, SIGIO, +// SIGVTALRM, SIGPROF, and SIGWINCH, plus some glibc-internal signals. +// +// 2. It shouldn't be used internally by libc in mixed Go/C binaries +// because libc may assume it's the only thing that can handle these +// signals. For example SIGCANCEL or SIGSETXID. +// +// 3. It should be a signal that can happen spuriously without +// consequences. For example, SIGALRM is a bad choice because the +// signal handler can't tell if it was caused by the real process +// alarm or not (arguably this means the signal is broken, but I +// digress). SIGUSR1 and SIGUSR2 are also bad because those are often +// used in meaningful ways by applications. +// +// 4. We need to deal with platforms without real-time signals (like +// macOS), so those are out. +// +// We use SIGURG because it meets all of these criteria, is extremely +// unlikely to be used by an application for its "real" meaning (both +// because out-of-band data is basically unused and because SIGURG +// doesn't report which socket has the condition, making it pretty +// useless), and even if it is, the application has to be ready for +// spurious SIGURG. SIGIO wouldn't be a bad choice either, but is more +// likely to be used for real. +const sigPreempt = _SIGURG + // Stores the signal handlers registered before Go installed its own. // These signal handlers will be invoked in cases where Go doesn't want to // handle a particular signal (e.g., signal occurred on a non-Go thread). @@ -290,6 +322,36 @@ func sigpipe() { dieFromSignal(_SIGPIPE) } +// doSigPreempt handles a preemption signal on gp. +func doSigPreempt(gp *g, ctxt *sigctxt) { + // Check if this G wants to be preempted and is safe to + // preempt. + if wantAsyncPreempt(gp) && isAsyncSafePoint(gp, ctxt.sigpc(), ctxt.sigsp()) { + // Inject a call to asyncPreempt. + ctxt.pushCall(funcPC(asyncPreempt)) + } + + // Acknowledge the preemption. + atomic.Xadd(&gp.m.preemptGen, 1) +} + +const preemptMSupported = pushCallSupported + +// preemptM sends a preemption request to mp. This request may be +// handled asynchronously and may be coalesced with other requests to +// the M. When the request is received, if the running G or P are +// marked for preemption and the goroutine is at an asynchronous +// safe-point, it will preempt the goroutine. It always atomically +// increments mp.preemptGen after handling a preemption request. +func preemptM(mp *m) { + if !pushCallSupported { + // This architecture doesn't support ctxt.pushCall + // yet, so doSigPreempt won't work. + return + } + signalM(mp, sigPreempt) +} + // sigFetchG fetches the value of G safely when running in a signal handler. // On some architectures, the g value may be clobbered when running in a VDSO. // See issue #32912. @@ -446,6 +508,14 @@ func sighandler(sig uint32, info *siginfo, ctxt unsafe.Pointer, gp *g) { return } + if sig == sigPreempt { + // Might be a preemption signal. + doSigPreempt(gp, c) + // Even if this was definitely a preemption signal, it + // may have been coalesced with another signal, so we + // still let it through to the application. + } + flags := int32(_SigThrow) if sig < uint32(len(sigtable)) { flags = sigtable[sig].flags |