Skip to content

Commit b0b0d98

Browse files
AndrewGMorganianlancetaylor
authored andcommitted
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 <[email protected]> Reviewed-by: Austin Clements <[email protected]> Trust: Tobias Klauser <[email protected]>
1 parent 223331f commit b0b0d98

File tree

7 files changed

+53
-11
lines changed

7 files changed

+53
-11
lines changed

src/runtime/os_js.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -72,7 +72,7 @@ func clearSignalHandlers() {
7272
}
7373

7474
//go:nosplit
75-
func sigblock() {
75+
func sigblock(exiting bool) {
7676
}
7777

7878
// Called to initialize a new m (including the bootstrap m).

src/runtime/os_linux.go

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -301,6 +301,24 @@ func getHugePageSize() uintptr {
301301
func osinit() {
302302
ncpu = getproccount()
303303
physHugePageSize = getHugePageSize()
304+
if iscgo {
305+
// #42494 glibc and musl reserve some signals for
306+
// internal use and require they not be blocked by
307+
// the rest of a normal C runtime. When the go runtime
308+
// blocks...unblocks signals, temporarily, the blocked
309+
// interval of time is generally very short. As such,
310+
// these expectations of *libc code are mostly met by
311+
// the combined go+cgo system of threads. However,
312+
// when go causes a thread to exit, via a return from
313+
// mstart(), the combined runtime can deadlock if
314+
// these signals are blocked. Thus, don't block these
315+
// signals when exiting threads.
316+
// - glibc: SIGCANCEL (32), SIGSETXID (33)
317+
// - musl: SIGTIMER (32), SIGCANCEL (33), SIGSYNCCALL (34)
318+
sigdelset(&sigsetAllExiting, 32)
319+
sigdelset(&sigsetAllExiting, 33)
320+
sigdelset(&sigsetAllExiting, 34)
321+
}
304322
osArchInit()
305323
}
306324

src/runtime/os_plan9.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -195,7 +195,7 @@ func msigrestore(sigmask sigset) {
195195
func clearSignalHandlers() {
196196
}
197197

198-
func sigblock() {
198+
func sigblock(exiting bool) {
199199
}
200200

201201
// Called to initialize a new m (including the bootstrap m).

src/runtime/os_windows.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -886,7 +886,7 @@ func clearSignalHandlers() {
886886
}
887887

888888
//go:nosplit
889-
func sigblock() {
889+
func sigblock(exiting bool) {
890890
}
891891

892892
// Called to initialize a new m (including the bootstrap m).

src/runtime/proc.go

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1313,7 +1313,7 @@ func mexit(osStack bool) {
13131313
throw("locked m0 woke up")
13141314
}
13151315

1316-
sigblock()
1316+
sigblock(true)
13171317
unminit()
13181318

13191319
// Free the gsignal stack.
@@ -1754,7 +1754,7 @@ func needm() {
17541754
// starting a new m to run Go code via newosproc.
17551755
var sigmask sigset
17561756
sigsave(&sigmask)
1757-
sigblock()
1757+
sigblock(false)
17581758

17591759
// Lock extra list, take head, unlock popped list.
17601760
// nilokay=false is safe here because of the invariant above,
@@ -1903,7 +1903,7 @@ func dropm() {
19031903
// Setg(nil) clears g, which is the signal handler's cue not to run Go handlers.
19041904
// It's important not to try to handle a signal between those two steps.
19051905
sigmask := mp.sigmask
1906-
sigblock()
1906+
sigblock(false)
19071907
unminit()
19081908

19091909
mnext := lockextra(true)
@@ -3776,7 +3776,7 @@ func beforefork() {
37763776
// group. See issue #18600.
37773777
gp.m.locks++
37783778
sigsave(&gp.m.sigmask)
3779-
sigblock()
3779+
sigblock(false)
37803780

37813781
// This function is called before fork in syscall package.
37823782
// Code between fork and exec must not allocate memory nor even try to grow stack.

src/runtime/signal_unix.go

Lines changed: 15 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1042,15 +1042,26 @@ func msigrestore(sigmask sigset) {
10421042
sigprocmask(_SIG_SETMASK, &sigmask, nil)
10431043
}
10441044

1045-
// sigblock blocks all signals in the current thread's signal mask.
1045+
// sigsetAllExiting is used by sigblock(true) when a thread is
1046+
// exiting. sigset_all is defined in OS specific code, and per GOOS
1047+
// behavior may override this default for sigsetAllExiting: see
1048+
// osinit().
1049+
var sigsetAllExiting = sigset_all
1050+
1051+
// sigblock blocks signals in the current thread's signal mask.
10461052
// This is used to block signals while setting up and tearing down g
1047-
// when a non-Go thread calls a Go function.
1048-
// The OS-specific code is expected to define sigset_all.
1053+
// when a non-Go thread calls a Go function. When a thread is exiting
1054+
// we use the sigsetAllExiting value, otherwise the OS specific
1055+
// definition of sigset_all is used.
10491056
// This is nosplit and nowritebarrierrec because it is called by needm
10501057
// which may be called on a non-Go thread with no g available.
10511058
//go:nosplit
10521059
//go:nowritebarrierrec
1053-
func sigblock() {
1060+
func sigblock(exiting bool) {
1061+
if exiting {
1062+
sigprocmask(_SIG_SETMASK, &sigsetAllExiting, nil)
1063+
return
1064+
}
10541065
sigprocmask(_SIG_SETMASK, &sigset_all, nil)
10551066
}
10561067

src/syscall/syscall_linux_test.go

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -597,6 +597,14 @@ func compareStatus(filter, expect string) error {
597597
return nil
598598
}
599599

600+
// killAThread locks the goroutine to an OS thread and exits; this
601+
// causes an OS thread to terminate.
602+
func killAThread(c <-chan struct{}) {
603+
runtime.LockOSThread()
604+
<-c
605+
return
606+
}
607+
600608
// TestSetuidEtc performs tests on all of the wrapped system calls
601609
// that mirror to the 9 glibc syscalls with POSIX semantics. The test
602610
// here is considered authoritative and should compile and run
@@ -647,6 +655,11 @@ func TestSetuidEtc(t *testing.T) {
647655
}
648656

649657
for i, v := range vs {
658+
// Generate some thread churn as we execute the tests.
659+
c := make(chan struct{})
660+
go killAThread(c)
661+
close(c)
662+
650663
if err := v.fn(); err != nil {
651664
t.Errorf("[%d] %q failed: %v", i, v.call, err)
652665
continue

0 commit comments

Comments
 (0)