mirror of
https://github.com/golang/go.git
synced 2025-10-19 19:13:18 +00:00
runtime: linux iscgo support for not blocking nptl signals
Under linux+cgo, OS threads are launched via pthread_create(). This abstraction, under linux, requires we avoid blocking signals 32,33 and 34 indefinitely because they are needed to reliably execute POSIX-semantics threading in glibc and/or musl. When blocking signals the go runtime generally re-enables them quickly. However, when a thread exits (under cgo, this is via a return from mstart()), we avoid a deadlock in C-code by not blocking these three signals. Fixes #42494 Change-Id: I02dfb2480a1f97d11679e0c4b132b51bddbe4c14 Reviewed-on: https://go-review.googlesource.com/c/go/+/269799 Reviewed-by: Ian Lance Taylor <iant@golang.org> Reviewed-by: Austin Clements <austin@google.com> Trust: Tobias Klauser <tobias.klauser@gmail.com>
This commit is contained in:
parent
223331fc0c
commit
b0b0d98283
7 changed files with 53 additions and 11 deletions
|
@ -72,7 +72,7 @@ func clearSignalHandlers() {
|
|||
}
|
||||
|
||||
//go:nosplit
|
||||
func sigblock() {
|
||||
func sigblock(exiting bool) {
|
||||
}
|
||||
|
||||
// Called to initialize a new m (including the bootstrap m).
|
||||
|
|
|
@ -301,6 +301,24 @@ func getHugePageSize() uintptr {
|
|||
func osinit() {
|
||||
ncpu = getproccount()
|
||||
physHugePageSize = getHugePageSize()
|
||||
if iscgo {
|
||||
// #42494 glibc and musl reserve some signals for
|
||||
// internal use and require they not be blocked by
|
||||
// the rest of a normal C runtime. When the go runtime
|
||||
// blocks...unblocks signals, temporarily, the blocked
|
||||
// interval of time is generally very short. As such,
|
||||
// these expectations of *libc code are mostly met by
|
||||
// the combined go+cgo system of threads. However,
|
||||
// when go causes a thread to exit, via a return from
|
||||
// mstart(), the combined runtime can deadlock if
|
||||
// these signals are blocked. Thus, don't block these
|
||||
// signals when exiting threads.
|
||||
// - glibc: SIGCANCEL (32), SIGSETXID (33)
|
||||
// - musl: SIGTIMER (32), SIGCANCEL (33), SIGSYNCCALL (34)
|
||||
sigdelset(&sigsetAllExiting, 32)
|
||||
sigdelset(&sigsetAllExiting, 33)
|
||||
sigdelset(&sigsetAllExiting, 34)
|
||||
}
|
||||
osArchInit()
|
||||
}
|
||||
|
||||
|
|
|
@ -195,7 +195,7 @@ func msigrestore(sigmask sigset) {
|
|||
func clearSignalHandlers() {
|
||||
}
|
||||
|
||||
func sigblock() {
|
||||
func sigblock(exiting bool) {
|
||||
}
|
||||
|
||||
// Called to initialize a new m (including the bootstrap m).
|
||||
|
|
|
@ -886,7 +886,7 @@ func clearSignalHandlers() {
|
|||
}
|
||||
|
||||
//go:nosplit
|
||||
func sigblock() {
|
||||
func sigblock(exiting bool) {
|
||||
}
|
||||
|
||||
// Called to initialize a new m (including the bootstrap m).
|
||||
|
|
|
@ -1313,7 +1313,7 @@ func mexit(osStack bool) {
|
|||
throw("locked m0 woke up")
|
||||
}
|
||||
|
||||
sigblock()
|
||||
sigblock(true)
|
||||
unminit()
|
||||
|
||||
// Free the gsignal stack.
|
||||
|
@ -1754,7 +1754,7 @@ func needm() {
|
|||
// starting a new m to run Go code via newosproc.
|
||||
var sigmask sigset
|
||||
sigsave(&sigmask)
|
||||
sigblock()
|
||||
sigblock(false)
|
||||
|
||||
// Lock extra list, take head, unlock popped list.
|
||||
// nilokay=false is safe here because of the invariant above,
|
||||
|
@ -1903,7 +1903,7 @@ func dropm() {
|
|||
// Setg(nil) clears g, which is the signal handler's cue not to run Go handlers.
|
||||
// It's important not to try to handle a signal between those two steps.
|
||||
sigmask := mp.sigmask
|
||||
sigblock()
|
||||
sigblock(false)
|
||||
unminit()
|
||||
|
||||
mnext := lockextra(true)
|
||||
|
@ -3776,7 +3776,7 @@ func beforefork() {
|
|||
// group. See issue #18600.
|
||||
gp.m.locks++
|
||||
sigsave(&gp.m.sigmask)
|
||||
sigblock()
|
||||
sigblock(false)
|
||||
|
||||
// This function is called before fork in syscall package.
|
||||
// Code between fork and exec must not allocate memory nor even try to grow stack.
|
||||
|
|
|
@ -1042,15 +1042,26 @@ func msigrestore(sigmask sigset) {
|
|||
sigprocmask(_SIG_SETMASK, &sigmask, nil)
|
||||
}
|
||||
|
||||
// sigblock blocks all signals in the current thread's signal mask.
|
||||
// sigsetAllExiting is used by sigblock(true) when a thread is
|
||||
// exiting. sigset_all is defined in OS specific code, and per GOOS
|
||||
// behavior may override this default for sigsetAllExiting: see
|
||||
// osinit().
|
||||
var sigsetAllExiting = sigset_all
|
||||
|
||||
// sigblock blocks signals in the current thread's signal mask.
|
||||
// This is used to block signals while setting up and tearing down g
|
||||
// when a non-Go thread calls a Go function.
|
||||
// The OS-specific code is expected to define sigset_all.
|
||||
// when a non-Go thread calls a Go function. When a thread is exiting
|
||||
// we use the sigsetAllExiting value, otherwise the OS specific
|
||||
// definition of sigset_all is used.
|
||||
// This is nosplit and nowritebarrierrec because it is called by needm
|
||||
// which may be called on a non-Go thread with no g available.
|
||||
//go:nosplit
|
||||
//go:nowritebarrierrec
|
||||
func sigblock() {
|
||||
func sigblock(exiting bool) {
|
||||
if exiting {
|
||||
sigprocmask(_SIG_SETMASK, &sigsetAllExiting, nil)
|
||||
return
|
||||
}
|
||||
sigprocmask(_SIG_SETMASK, &sigset_all, nil)
|
||||
}
|
||||
|
||||
|
|
|
@ -597,6 +597,14 @@ func compareStatus(filter, expect string) error {
|
|||
return nil
|
||||
}
|
||||
|
||||
// killAThread locks the goroutine to an OS thread and exits; this
|
||||
// causes an OS thread to terminate.
|
||||
func killAThread(c <-chan struct{}) {
|
||||
runtime.LockOSThread()
|
||||
<-c
|
||||
return
|
||||
}
|
||||
|
||||
// TestSetuidEtc performs tests on all of the wrapped system calls
|
||||
// that mirror to the 9 glibc syscalls with POSIX semantics. The test
|
||||
// here is considered authoritative and should compile and run
|
||||
|
@ -647,6 +655,11 @@ func TestSetuidEtc(t *testing.T) {
|
|||
}
|
||||
|
||||
for i, v := range vs {
|
||||
// Generate some thread churn as we execute the tests.
|
||||
c := make(chan struct{})
|
||||
go killAThread(c)
|
||||
close(c)
|
||||
|
||||
if err := v.fn(); err != nil {
|
||||
t.Errorf("[%d] %q failed: %v", i, v.call, err)
|
||||
continue
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue