Skip to content

Commit

Permalink
runtime: don't always unblock all signals
Browse files Browse the repository at this point in the history
Ian proposed an improved way of handling signals masks in Go, motivated
by a problem where the Android java runtime expects certain signals to
be blocked for all JVM threads. Discussion here

https://groups.google.com/forum/#!topic/golang-dev/_TSCkQHJt6g

Ian's text is used in the following:

A Go program always needs to have the synchronous signals enabled.
These are the signals for which _SigPanic is set in sigtable, namely
SIGSEGV, SIGBUS, SIGFPE.

A Go program that uses the os/signal package, and calls signal.Notify,
needs to have at least one thread which is not blocking that signal,
but it doesn't matter much which one.

Unix programs do not change signal mask across execve.  They inherit
signal masks across fork.  The shell uses this fact to some extent;
for example, the job control signals (SIGTTIN, SIGTTOU, SIGTSTP) are
blocked for commands run due to backquote quoting or $().

Our current position on signal masks was not thought out.  We wandered
into step by step, e.g., http://golang.org/cl/7323067 .

This CL does the following:

Introduce a new platform hook, msigsave, that saves the signal mask of
the current thread to m.sigsave.

Call msigsave from needm and newm.

In minit grab set up the signal mask from m.sigsave and unblock the
essential synchronous signals, and SIGILL, SIGTRAP, SIGPROF, SIGSTKFLT
(for systems that have it).

In unminit, restore the signal mask from m.sigsave.

The first time that os/signal.Notify is called, start a new thread whose
only purpose is to update its signal mask to make sure signals for
signal.Notify are unblocked on at least one thread.

The effect on Go programs will be that if they are invoked with some
non-synchronous signals blocked, those signals will normally be
ignored.  Previously, those signals would mostly be ignored.  A change
in behaviour will occur for programs started with any of these signals
blocked, if they receive the signal: SIGHUP, SIGINT, SIGQUIT, SIGABRT,
SIGTERM.  Previously those signals would always cause a crash (unless
using the os/signal package); with this change, they will be ignored
if the program is started with the signal blocked (and does not use
the os/signal package).

./all.bash completes successfully on linux/amd64.

OpenBSD is missing the implementation.

Change-Id: I188098ba7eb85eae4c14861269cc466f2aa40e8c
Reviewed-on: https://go-review.googlesource.com/10173
Reviewed-by: Ian Lance Taylor <[email protected]>
  • Loading branch information
Elias Naur authored and ianlancetaylor committed May 22, 2015
1 parent 994b2d4 commit 84cfba1
Show file tree
Hide file tree
Showing 20 changed files with 358 additions and 80 deletions.
9 changes: 5 additions & 4 deletions misc/cgo/test/cgo_linux_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,8 @@ package cgotest

import "testing"

func TestSetgid(t *testing.T) { testSetgid(t) }
func Test6997(t *testing.T) { test6997(t) }
func TestBuildID(t *testing.T) { testBuildID(t) }
func Test9400(t *testing.T) { test9400(t) }
func TestSetgid(t *testing.T) { testSetgid(t) }
func Test6997(t *testing.T) { test6997(t) }
func TestBuildID(t *testing.T) { testBuildID(t) }
func Test9400(t *testing.T) { test9400(t) }
func TestSigProcMask(t *testing.T) { testSigProcMask(t) }
35 changes: 35 additions & 0 deletions misc/cgo/test/sigprocmask_linux.c
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
// Copyright 2015 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.

#include <signal.h>
#include <stdlib.h>
#include <pthread.h>
#include <stdio.h>
#include <unistd.h>

extern void IntoGoAndBack();

int CheckBlocked() {
sigset_t mask;
sigprocmask(SIG_BLOCK, NULL, &mask);
return sigismember(&mask, SIGIO);
}

static void* sigthreadfunc(void* unused) {
sigset_t mask;
sigemptyset(&mask);
sigaddset(&mask, SIGIO);
sigprocmask(SIG_BLOCK, &mask, NULL);
IntoGoAndBack();
}

int RunSigThread() {
pthread_t thread;
int r;

r = pthread_create(&thread, NULL, &sigthreadfunc, NULL);
if (r != 0)
return r;
return pthread_join(thread, NULL);
}
38 changes: 38 additions & 0 deletions misc/cgo/test/sigprocmask_linux.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
// Copyright 2015 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.

package cgotest

/*
#cgo CFLAGS: -pthread
#cgo LDFLAGS: -pthread
extern int RunSigThread();
extern int CheckBlocked();
*/
import "C"
import (
"os"
"os/signal"
"syscall"
"testing"
)

var blocked bool

//export IntoGoAndBack
func IntoGoAndBack() {
// Verify that SIGIO stays blocked on the C thread
// even when unblocked for signal.Notify().
signal.Notify(make(chan os.Signal), syscall.SIGIO)
blocked = C.CheckBlocked() != 0
}

func testSigProcMask(t *testing.T) {
if r := C.RunSigThread(); r != 0 {
t.Error("pthread_create/pthread_join failed")
}
if !blocked {
t.Error("Go runtime unblocked SIGIO")
}
}
26 changes: 22 additions & 4 deletions src/runtime/os1_darwin.go
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,6 @@ import "unsafe"

//extern SigTabTT runtime·sigtab[];

var sigset_none = uint32(0)
var sigset_all = ^uint32(0)

func unimplemented(name string) {
Expand Down Expand Up @@ -126,17 +125,36 @@ func mpreinit(mp *m) {
mp.gsignal.m = mp
}

func msigsave(mp *m) {
smask := (*uint32)(unsafe.Pointer(&mp.sigmask))
if unsafe.Sizeof(*smask) > unsafe.Sizeof(mp.sigmask) {
throw("insufficient storage for signal mask")
}
sigprocmask(_SIG_SETMASK, nil, smask)
}

// Called to initialize a new m (including the bootstrap m).
// Called on the new thread, can not allocate memory.
func minit() {
// Initialize signal handling.
_g_ := getg()
signalstack((*byte)(unsafe.Pointer(_g_.m.gsignal.stack.lo)), 32*1024)
sigprocmask(_SIG_SETMASK, &sigset_none, nil)

// restore signal mask from m.sigmask and unblock essential signals
nmask := *(*uint32)(unsafe.Pointer(&_g_.m.sigmask))
for i := range sigtable {
if sigtable[i].flags&_SigUnblock != 0 {
nmask &^= 1 << (uint32(i) - 1)
}
}
sigprocmask(_SIG_SETMASK, &nmask, nil)
}

// Called from dropm to undo the effect of an minit.
func unminit() {
_g_ := getg()
smask := (*uint32)(unsafe.Pointer(&_g_.m.sigmask))
sigprocmask(_SIG_SETMASK, smask, nil)
signalstack(nil, 0)
}

Expand Down Expand Up @@ -447,6 +465,6 @@ func signalstack(p *byte, n int32) {
sigaltstack(&st, nil)
}

func unblocksignals() {
sigprocmask(_SIG_SETMASK, &sigset_none, nil)
func updatesigmask(m sigmask) {
sigprocmask(_SIG_SETMASK, &m[0], nil)
}
28 changes: 24 additions & 4 deletions src/runtime/os1_dragonfly.go
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,6 @@ const (
_HW_NCPU = 3
)

var sigset_none = sigset{}
var sigset_all = sigset{[4]uint32{^uint32(0), ^uint32(0), ^uint32(0), ^uint32(0)}}

func getncpu() int32 {
Expand Down Expand Up @@ -120,6 +119,14 @@ func mpreinit(mp *m) {
mp.gsignal.m = mp
}

func msigsave(mp *m) {
smask := (*sigset)(unsafe.Pointer(&mp.sigmask))
if unsafe.Sizeof(*smask) > unsafe.Sizeof(mp.sigmask) {
throw("insufficient storage for signal mask")
}
sigprocmask(nil, smask)
}

// Called to initialize a new m (including the bootstrap m).
// Called on the new thread, can not allocate memory.
func minit() {
Expand All @@ -130,11 +137,22 @@ func minit() {

// Initialize signal handling
signalstack((*byte)(unsafe.Pointer(_g_.m.gsignal.stack.lo)), 32*1024)
sigprocmask(&sigset_none, nil)

// restore signal mask from m.sigmask and unblock essential signals
nmask := *(*sigset)(unsafe.Pointer(&_g_.m.sigmask))
for i := range sigtable {
if sigtable[i].flags&_SigUnblock != 0 {
nmask.__bits[(i-1)/32] &^= 1 << ((uint32(i) - 1) & 31)
}
}
sigprocmask(&nmask, nil)
}

// Called from dropm to undo the effect of an minit.
func unminit() {
_g_ := getg()
smask := (*sigset)(unsafe.Pointer(&_g_.m.sigmask))
sigprocmask(smask, nil)
signalstack(nil, 0)
}

Expand Down Expand Up @@ -215,6 +233,8 @@ func signalstack(p *byte, n int32) {
sigaltstack(&st, nil)
}

func unblocksignals() {
sigprocmask(&sigset_none, nil)
func updatesigmask(m sigmask) {
var mask sigset
copy(mask.__bits[:], m[:])
sigprocmask(&mask, nil)
}
28 changes: 24 additions & 4 deletions src/runtime/os1_freebsd.go
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,6 @@ const (
_HW_NCPU = 3
)

var sigset_none = sigset{}
var sigset_all = sigset{[4]uint32{^uint32(0), ^uint32(0), ^uint32(0), ^uint32(0)}}

func getncpu() int32 {
Expand Down Expand Up @@ -119,6 +118,14 @@ func mpreinit(mp *m) {
mp.gsignal.m = mp
}

func msigsave(mp *m) {
smask := (*sigset)(unsafe.Pointer(&mp.sigmask))
if unsafe.Sizeof(*smask) > unsafe.Sizeof(mp.sigmask) {
throw("insufficient storage for signal mask")
}
sigprocmask(nil, smask)
}

// Called to initialize a new m (including the bootstrap m).
// Called on the new thread, can not allocate memory.
func minit() {
Expand All @@ -132,11 +139,22 @@ func minit() {

// Initialize signal handling.
signalstack((*byte)(unsafe.Pointer(_g_.m.gsignal.stack.lo)), 32*1024)
sigprocmask(&sigset_none, nil)

// restore signal mask from m.sigmask and unblock essential signals
nmask := *(*sigset)(unsafe.Pointer(&_g_.m.sigmask))
for i := range sigtable {
if sigtable[i].flags&_SigUnblock != 0 {
nmask.__bits[(i-1)/32] &^= 1 << ((uint32(i) - 1) & 31)
}
}
sigprocmask(&nmask, nil)
}

// Called from dropm to undo the effect of an minit.
func unminit() {
_g_ := getg()
smask := (*sigset)(unsafe.Pointer(&_g_.m.sigmask))
sigprocmask(smask, nil)
signalstack(nil, 0)
}

Expand Down Expand Up @@ -217,6 +235,8 @@ func signalstack(p *byte, n int32) {
sigaltstack(&st, nil)
}

func unblocksignals() {
sigprocmask(&sigset_none, nil)
func updatesigmask(m [(_NSIG + 31) / 32]uint32) {
var mask sigset
copy(mask.__bits[:], m[:])
sigprocmask(&mask, nil)
}
28 changes: 24 additions & 4 deletions src/runtime/os1_linux.go
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,6 @@ package runtime

import "unsafe"

var sigset_none sigset
var sigset_all sigset = sigset{^uint32(0), ^uint32(0)}

// Linux futex.
Expand Down Expand Up @@ -190,17 +189,36 @@ func mpreinit(mp *m) {
mp.gsignal.m = mp
}

func msigsave(mp *m) {
smask := (*sigset)(unsafe.Pointer(&mp.sigmask))
if unsafe.Sizeof(*smask) > unsafe.Sizeof(mp.sigmask) {
throw("insufficient storage for signal mask")
}
rtsigprocmask(_SIG_SETMASK, nil, smask, int32(unsafe.Sizeof(*smask)))
}

// Called to initialize a new m (including the bootstrap m).
// Called on the new thread, can not allocate memory.
func minit() {
// Initialize signal handling.
_g_ := getg()
signalstack((*byte)(unsafe.Pointer(_g_.m.gsignal.stack.lo)), 32*1024)
rtsigprocmask(_SIG_SETMASK, &sigset_none, nil, int32(unsafe.Sizeof(sigset_none)))

// restore signal mask from m.sigmask and unblock essential signals
nmask := *(*sigset)(unsafe.Pointer(&_g_.m.sigmask))
for i := range sigtable {
if sigtable[i].flags&_SigUnblock != 0 {
nmask[(i-1)/32] &^= 1 << ((uint32(i) - 1) & 31)
}
}
rtsigprocmask(_SIG_SETMASK, &nmask, nil, int32(unsafe.Sizeof(nmask)))
}

// Called from dropm to undo the effect of an minit.
func unminit() {
_g_ := getg()
smask := (*sigset)(unsafe.Pointer(&_g_.m.sigmask))
rtsigprocmask(_SIG_SETMASK, smask, nil, int32(unsafe.Sizeof(*smask)))
signalstack(nil, 0)
}

Expand Down Expand Up @@ -304,6 +322,8 @@ func signalstack(p *byte, n int32) {
sigaltstack(&st, nil)
}

func unblocksignals() {
rtsigprocmask(_SIG_SETMASK, &sigset_none, nil, int32(unsafe.Sizeof(sigset_none)))
func updatesigmask(m sigmask) {
var mask sigset
copy(mask[:], m[:])
rtsigprocmask(_SIG_SETMASK, &mask, nil, int32(unsafe.Sizeof(mask)))
}
3 changes: 3 additions & 0 deletions src/runtime/os1_nacl.go
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,9 @@ func mpreinit(mp *m) {

func sigtramp()

func msigsave(mp *m) {
}

// Called to initialize a new m (including the bootstrap m).
// Called on the new thread, can not allocate memory.
func minit() {
Expand Down
29 changes: 25 additions & 4 deletions src/runtime/os1_netbsd.go
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,6 @@ const (
_CLOCK_MONOTONIC = 3
)

var sigset_none = sigset{}
var sigset_all = sigset{[4]uint32{^uint32(0), ^uint32(0), ^uint32(0), ^uint32(0)}}

// From NetBSD's <sys/sysctl.h>
Expand Down Expand Up @@ -139,6 +138,14 @@ func mpreinit(mp *m) {
mp.gsignal.m = mp
}

func msigsave(mp *m) {
smask := (*sigset)(unsafe.Pointer(&mp.sigmask))
if unsafe.Sizeof(*smask) > unsafe.Sizeof(mp.sigmask) {
throw("insufficient storage for signal mask")
}
sigprocmask(_SIG_SETMASK, nil, smask)
}

// Called to initialize a new m (including the bootstrap m).
// Called on the new thread, can not allocate memory.
func minit() {
Expand All @@ -147,11 +154,23 @@ func minit() {

// Initialize signal handling
signalstack((*byte)(unsafe.Pointer(_g_.m.gsignal.stack.lo)), 32*1024)
sigprocmask(_SIG_SETMASK, &sigset_none, nil)

// restore signal mask from m.sigmask and unblock essential signals
nmask := *(*sigset)(unsafe.Pointer(&_g_.m.sigmask))
for i := range sigtable {
if sigtable[i].flags&_SigUnblock != 0 {
nmask.__bits[(i-1)/32] &^= 1 << ((uint32(i) - 1) & 31)
}
}
sigprocmask(_SIG_SETMASK, &nmask, nil)
}

// Called from dropm to undo the effect of an minit.
func unminit() {
_g_ := getg()
smask := (*sigset)(unsafe.Pointer(&_g_.m.sigmask))
sigprocmask(_SIG_SETMASK, smask, nil)

signalstack(nil, 0)
}

Expand Down Expand Up @@ -206,6 +225,8 @@ func signalstack(p *byte, n int32) {
sigaltstack(&st, nil)
}

func unblocksignals() {
sigprocmask(_SIG_SETMASK, &sigset_none, nil)
func updatesigmask(m sigmask) {
var mask sigset
copy(mask.__bits[:], m[:])
sigprocmask(_SIG_SETMASK, &mask, nil)
}
Loading

0 comments on commit 84cfba1

Please sign in to comment.