]> Cypherpunks repositories - gostls13.git/commitdiff
runtime: linux iscgo support for not blocking nptl signals
authorAndrew G. Morgan <agm@google.com>
Fri, 13 Nov 2020 05:19:52 +0000 (21:19 -0800)
committerIan Lance Taylor <iant@golang.org>
Wed, 23 Dec 2020 02:10:51 +0000 (02:10 +0000)
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>

src/runtime/os_js.go
src/runtime/os_linux.go
src/runtime/os_plan9.go
src/runtime/os_windows.go
src/runtime/proc.go
src/runtime/signal_unix.go
src/syscall/syscall_linux_test.go

index 94983b358d4abdc604a84fcb488e3628bc81e397..91d18a078f8b301380e780f16b61cba2bef8a004 100644 (file)
@@ -72,7 +72,7 @@ func clearSignalHandlers() {
 }
 
 //go:nosplit
-func sigblock() {
+func sigblock(exiting bool) {
 }
 
 // Called to initialize a new m (including the bootstrap m).
index 371db73502915d92d360b41713854e9fe1df7512..f122d2c2ef7c754bcb122c9affb3b7c5231e678c 100644 (file)
@@ -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()
 }
 
index 62aecea06041de4bb65afdf959d9c18526915bff..a0355269373f10a7c009ec9443ecc785c9e540c3 100644 (file)
@@ -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).
index ffb087f9db2794794ab2308dc642f7b57bb3b620..d389d38ab9cf0cb11d377a188ede27c40025f212 100644 (file)
@@ -886,7 +886,7 @@ func clearSignalHandlers() {
 }
 
 //go:nosplit
-func sigblock() {
+func sigblock(exiting bool) {
 }
 
 // Called to initialize a new m (including the bootstrap m).
index 5adcbf07dcba6a1c082d0578d991c95f1c1aca75..592d621241cba21bdea5c3f0a8058921528235b9 100644 (file)
@@ -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.
index e8f39c3321f9272cccc5a3cb9a3a38d72f13f526..382ba37a8784b09d832e18870c8b465a34729deb 100644 (file)
@@ -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)
 }
 
index 153d0efef11f89917f037dc11d84a961a9af94b4..adeb7c9ebbf20d61114a59e74b16c593eb129279 100644 (file)
@@ -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