]> Cypherpunks repositories - gostls13.git/commitdiff
[release-branch.go1.15] runtime: block signals in needm before allocating M
authorIan Lance Taylor <iant@golang.org>
Tue, 27 Oct 2020 23:09:40 +0000 (16:09 -0700)
committerIan Lance Taylor <iant@golang.org>
Fri, 20 Nov 2020 20:38:12 +0000 (20:38 +0000)
Otherwise, if a signal occurs just after we allocated the M,
we can deadlock if the signal handler needs to allocate an M
itself.

For #42207
Fixes #42636

Change-Id: I76f44547f419e8b1c14cbf49bf602c6e645d8c14
Reviewed-on: https://go-review.googlesource.com/c/go/+/265759
Trust: Ian Lance Taylor <iant@golang.org>
Run-TryBot: Ian Lance Taylor <iant@golang.org>
TryBot-Result: Go Bot <gobot@golang.org>
Reviewed-by: Bryan C. Mills <bcmills@google.com>
(cherry picked from commit 368c40116434532dc0b53b72fa04788ca6742898)
Reviewed-on: https://go-review.googlesource.com/c/go/+/271847

src/runtime/crash_cgo_test.go
src/runtime/os_js.go
src/runtime/os_plan9.go
src/runtime/os_windows.go
src/runtime/proc.go
src/runtime/signal_unix.go
src/runtime/testdata/testprogcgo/needmdeadlock.go [new file with mode: 0644]

index 4872189f16beeb7631ead4466121633acc12a5b4..57c3639f71073660fdede2be703edf03d490799e 100644 (file)
@@ -600,3 +600,16 @@ func TestEINTR(t *testing.T) {
                t.Fatalf("want %s, got %s\n", want, output)
        }
 }
+
+// Issue #42207.
+func TestNeedmDeadlock(t *testing.T) {
+       switch runtime.GOOS {
+       case "plan9", "windows":
+               t.Skipf("no signals on %s", runtime.GOOS)
+       }
+       output := runTestProg(t, "testprogcgo", "NeedmDeadlock")
+       want := "OK\n"
+       if output != want {
+               t.Fatalf("want %s, got %s\n", want, output)
+       }
+}
index ff0ee3aa6be8dfd4ae3adfbb82c9eaeaa4f18031..94983b358d4abdc604a84fcb488e3628bc81e397 100644 (file)
@@ -59,7 +59,7 @@ func mpreinit(mp *m) {
 }
 
 //go:nosplit
-func msigsave(mp *m) {
+func sigsave(p *sigset) {
 }
 
 //go:nosplit
index 9e187d2220514cdbb0a0352b0e66403e94ba2940..8f5380d3d908c6e6283e901f7d3e46fe8347b7d1 100644 (file)
@@ -167,7 +167,7 @@ func mpreinit(mp *m) {
        mp.errstr = (*byte)(mallocgc(_ERRMAX, nil, true))
 }
 
-func msigsave(mp *m) {
+func sigsave(p *sigset) {
 }
 
 func msigrestore(sigmask sigset) {
index 769197db46e359ab39f0cbd73898b23c60016c4a..81d9ccf8f088393157407ad9306a4f95833c283f 100644 (file)
@@ -830,7 +830,7 @@ func mpreinit(mp *m) {
 }
 
 //go:nosplit
-func msigsave(mp *m) {
+func sigsave(p *sigset) {
 }
 
 //go:nosplit
index 2f1272d310b6f401d1b00dd21bfe638813e2335f..7fa19d867b44c9449a21f498037f6fcaa8d2e139 100644 (file)
@@ -569,7 +569,7 @@ func schedinit() {
        typelinksinit() // uses maps, activeModules
        itabsinit()     // uses activeModules
 
-       msigsave(_g_.m)
+       sigsave(&_g_.m.sigmask)
        initSigmask = _g_.m.sigmask
 
        goargs()
@@ -1536,6 +1536,18 @@ func needm(x byte) {
                exit(1)
        }
 
+       // Save and block signals before getting an M.
+       // The signal handler may call needm itself,
+       // and we must avoid a deadlock. Also, once g is installed,
+       // any incoming signals will try to execute,
+       // but we won't have the sigaltstack settings and other data
+       // set up appropriately until the end of minit, which will
+       // unblock the signals. This is the same dance as when
+       // starting a new m to run Go code via newosproc.
+       var sigmask sigset
+       sigsave(&sigmask)
+       sigblock()
+
        // Lock extra list, take head, unlock popped list.
        // nilokay=false is safe here because of the invariant above,
        // that the extra list always contains or will soon contain
@@ -1553,14 +1565,8 @@ func needm(x byte) {
        extraMCount--
        unlockextra(mp.schedlink.ptr())
 
-       // Save and block signals before installing g.
-       // Once g is installed, any incoming signals will try to execute,
-       // but we won't have the sigaltstack settings and other data
-       // set up appropriately until the end of minit, which will
-       // unblock the signals. This is the same dance as when
-       // starting a new m to run Go code via newosproc.
-       msigsave(mp)
-       sigblock()
+       // Store the original signal mask for use by minit.
+       mp.sigmask = sigmask
 
        // Install g (= m->g0) and set the stack bounds
        // to match the current stack. We don't actually know
@@ -3417,7 +3423,7 @@ func beforefork() {
        // a signal handler before exec if a signal is sent to the process
        // group. See issue #18600.
        gp.m.locks++
-       msigsave(gp.m)
+       sigsave(&gp.m.sigmask)
        sigblock()
 
        // This function is called before fork in syscall package.
index 80fd2d660491f111f5efade781cb663201b8a344..003c7b0bc81e49fe18e5157e44b668d09ef92d00 100644 (file)
@@ -1032,15 +1032,15 @@ func sigfwdgo(sig uint32, info *siginfo, ctx unsafe.Pointer) bool {
        return true
 }
 
-// msigsave saves the current thread's signal mask into mp.sigmask.
+// sigsave saves the current thread's signal mask into *p.
 // This is used to preserve the non-Go signal mask when a non-Go
 // thread calls a Go function.
 // 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 msigsave(mp *m) {
-       sigprocmask(_SIG_SETMASK, nil, &mp.sigmask)
+func sigsave(p *sigset) {
+       sigprocmask(_SIG_SETMASK, nil, p)
 }
 
 // msigrestore sets the current thread's signal mask to sigmask.
@@ -1112,7 +1112,7 @@ func minitSignalStack() {
 // thread's signal mask. When this is called all signals have been
 // blocked for the thread.  This starts with m.sigmask, which was set
 // either from initSigmask for a newly created thread or by calling
-// msigsave if this is a non-Go thread calling a Go function. It
+// sigsave if this is a non-Go thread calling a Go function. It
 // removes all essential signals from the mask, thus causing those
 // signals to not be blocked. Then it sets the thread's signal mask.
 // After this is called the thread can receive signals.
diff --git a/src/runtime/testdata/testprogcgo/needmdeadlock.go b/src/runtime/testdata/testprogcgo/needmdeadlock.go
new file mode 100644 (file)
index 0000000..5a9c359
--- /dev/null
@@ -0,0 +1,95 @@
+// Copyright 2020 The Go Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style
+// license that can be found in the LICENSE file.
+
+// +build !plan9,!windows
+
+package main
+
+// This is for issue #42207.
+// During a call to needm we could get a SIGCHLD signal
+// which would itself call needm, causing a deadlock.
+
+/*
+#include <signal.h>
+#include <pthread.h>
+#include <sched.h>
+#include <unistd.h>
+
+extern void GoNeedM();
+
+#define SIGNALERS 10
+
+static void* needmSignalThread(void* p) {
+       pthread_t* pt = (pthread_t*)(p);
+       int i;
+
+       for (i = 0; i < 100; i++) {
+               if (pthread_kill(*pt, SIGCHLD) < 0) {
+                       return NULL;
+               }
+               usleep(1);
+       }
+       return NULL;
+}
+
+// We don't need many calls, as the deadlock is only likely
+// to occur the first couple of times that needm is called.
+// After that there will likely be an extra M available.
+#define CALLS 10
+
+static void* needmCallbackThread(void* p) {
+       int i;
+
+       for (i = 0; i < SIGNALERS; i++) {
+               sched_yield(); // Help the signal threads get started.
+       }
+       for (i = 0; i < CALLS; i++) {
+               GoNeedM();
+       }
+       return NULL;
+}
+
+static void runNeedmSignalThread() {
+       int i;
+       pthread_t caller;
+       pthread_t s[SIGNALERS];
+
+       pthread_create(&caller, NULL, needmCallbackThread, NULL);
+       for (i = 0; i < SIGNALERS; i++) {
+               pthread_create(&s[i], NULL, needmSignalThread, &caller);
+       }
+       for (i = 0; i < SIGNALERS; i++) {
+               pthread_join(s[i], NULL);
+       }
+       pthread_join(caller, NULL);
+}
+*/
+import "C"
+
+import (
+       "fmt"
+       "os"
+       "time"
+)
+
+func init() {
+       register("NeedmDeadlock", NeedmDeadlock)
+}
+
+//export GoNeedM
+func GoNeedM() {
+}
+
+func NeedmDeadlock() {
+       // The failure symptom is that the program hangs because of a
+       // deadlock in needm, so set an alarm.
+       go func() {
+               time.Sleep(5 * time.Second)
+               fmt.Println("Hung for 5 seconds")
+               os.Exit(1)
+       }()
+
+       C.runNeedmSignalThread()
+       fmt.Println("OK")
+}