Skip to content

Commit 3115952

Browse files
author
Elias Naur
committed
runtime: more gentle handling of signal masks
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: In minit, save the signal mask, and unblock the synchronous signals, and SIGILL, SIGTRAP, SIGPROF, SIGSTKFLT (for systems that have it), and any signals for which os/signal.Notify is currently active. The latter set of signals can be reliably maintained by sigenable/sigdisable. In unminit, restore the signal mask. In c-archive/c-shared mode, the first time that os/signal.Notify is called, start a new thread, that calls minit as usual, and then goes to sleep. The purpose of this is to ensure that there is always a thread that is not blocking the notified signals. 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). This CL only implements the changes for linux, darwin, netbsd, and solaris. Dragonfly and freebsd are missing the how parameter in the assembly implementation of sigprocmask, and openbsd is missing the oldset parameter. ./all.bash completes successfully on linux/amd64. Change-Id: I188098ba7eb85eae4c14861269cc466f2aa40e8c
1 parent 82833b3 commit 3115952

18 files changed

+277
-57
lines changed

misc/cgo/test/cgo_linux_test.go

Lines changed: 5 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,8 @@ package cgotest
66

77
import "testing"
88

9-
func TestSetgid(t *testing.T) { testSetgid(t) }
10-
func Test6997(t *testing.T) { test6997(t) }
11-
func TestBuildID(t *testing.T) { testBuildID(t) }
12-
func Test9400(t *testing.T) { test9400(t) }
9+
func TestSetgid(t *testing.T) { testSetgid(t) }
10+
func Test6997(t *testing.T) { test6997(t) }
11+
func TestBuildID(t *testing.T) { testBuildID(t) }
12+
func Test9400(t *testing.T) { test9400(t) }
13+
func TestSigProcMask(t *testing.T) { testSigProcMask(t) }

misc/cgo/test/sigprocmask_linux.c

Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,35 @@
1+
// Copyright 2015 The Go Authors. All rights reserved.
2+
// Use of this source code is governed by a BSD-style
3+
// license that can be found in the LICENSE file.
4+
5+
#include <signal.h>
6+
#include <stdlib.h>
7+
#include <pthread.h>
8+
#include <stdio.h>
9+
#include <unistd.h>
10+
11+
extern void IntoGoAndBack();
12+
13+
int CheckBlocked() {
14+
sigset_t mask;
15+
sigprocmask(SIG_BLOCK, NULL, &mask);
16+
return sigismember(&mask, SIGIO);
17+
}
18+
19+
static void* sigthreadfunc(void* unused) {
20+
sigset_t mask;
21+
sigemptyset(&mask);
22+
sigaddset(&mask, SIGIO);
23+
sigprocmask(SIG_BLOCK, &mask, NULL);
24+
IntoGoAndBack();
25+
}
26+
27+
int RunSigThread() {
28+
pthread_t thread;
29+
int r;
30+
31+
r = pthread_create(&thread, NULL, &sigthreadfunc, NULL);
32+
if (r != 0)
33+
return r;
34+
return pthread_join(thread, NULL);
35+
}

misc/cgo/test/sigprocmask_linux.go

Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,30 @@
1+
// Copyright 2015 The Go Authors. All rights reserved.
2+
// Use of this source code is governed by a BSD-style
3+
// license that can be found in the LICENSE file.
4+
5+
package cgotest
6+
7+
/*
8+
#cgo CFLAGS: -pthread
9+
#cgo LDFLAGS: -pthread
10+
extern int RunSigThread();
11+
extern int CheckBlocked();
12+
*/
13+
import "C"
14+
import "testing"
15+
16+
var blocked bool
17+
18+
//export IntoGoAndBack
19+
func IntoGoAndBack() {
20+
blocked = C.CheckBlocked() != 0
21+
}
22+
23+
func testSigProcMask(t *testing.T) {
24+
if r := C.RunSigThread(); r != 0 {
25+
t.Error("pthread_create/pthread_join failed")
26+
}
27+
if !blocked {
28+
t.Error("Go runtime unblocked SIGIO")
29+
}
30+
}

src/runtime/os1_darwin.go

Lines changed: 23 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -132,11 +132,28 @@ func minit() {
132132
// Initialize signal handling.
133133
_g_ := getg()
134134
signalstack((*byte)(unsafe.Pointer(_g_.m.gsignal.stack.lo)), 32*1024)
135-
sigprocmask(_SIG_SETMASK, &sigset_none, nil)
135+
136+
smask := (*uint32)(unsafe.Pointer(&_g_.m.sigmask))
137+
if unsafe.Sizeof(*smask) > unsafe.Sizeof(_g_.m.sigmask) {
138+
throw("insufficient storage for signal mask")
139+
}
140+
141+
// save current signal mask for muninit and unblock essential signals.
142+
var nset uint32 = sigActive[0]
143+
for i := range sigtable {
144+
if sigtable[i].flags&_SigUnblock != 0 {
145+
nset |= 1 << (uint32(i) - 1)
146+
}
147+
}
148+
sigprocmask(_SIG_UNBLOCK, &nset, nil)
136149
}
137150

138151
// Called from dropm to undo the effect of an minit.
139152
func unminit() {
153+
_g_ := getg()
154+
smask := (*uint32)(unsafe.Pointer(&_g_.m.sigmask))
155+
sigprocmask(_SIG_SETMASK, smask, nil)
156+
140157
signalstack(nil, 0)
141158
}
142159

@@ -447,6 +464,11 @@ func signalstack(p *byte, n int32) {
447464
sigaltstack(&st, nil)
448465
}
449466

467+
func unblocksig(i uint32) {
468+
var sset uint32 = 1 << (i - 1)
469+
sigprocmask(_SIG_UNBLOCK, &sset, nil)
470+
}
471+
450472
func unblocksignals() {
451473
sigprocmask(_SIG_SETMASK, &sigset_none, nil)
452474
}

src/runtime/os1_dragonfly.go

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -215,6 +215,10 @@ func signalstack(p *byte, n int32) {
215215
sigaltstack(&st, nil)
216216
}
217217

218+
func unblocksig(sig uint32) {
219+
// TODO(elias.naur): Implement when sigprocmask takes a _SIG_UNBLOCK parameter
220+
}
221+
218222
func unblocksignals() {
219223
sigprocmask(&sigset_none, nil)
220224
}

src/runtime/os1_freebsd.go

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -217,6 +217,10 @@ func signalstack(p *byte, n int32) {
217217
sigaltstack(&st, nil)
218218
}
219219

220+
func unblocksig(sig uint32) {
221+
// TODO(elias.naur): Implement when sigprocmask takes a _SIG_UNBLOCK parameter
222+
}
223+
220224
func unblocksignals() {
221225
sigprocmask(&sigset_none, nil)
222226
}

src/runtime/os1_linux.go

Lines changed: 26 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -196,11 +196,28 @@ func minit() {
196196
// Initialize signal handling.
197197
_g_ := getg()
198198
signalstack((*byte)(unsafe.Pointer(_g_.m.gsignal.stack.lo)), 32*1024)
199-
rtsigprocmask(_SIG_SETMASK, &sigset_none, nil, int32(unsafe.Sizeof(sigset_none)))
199+
200+
smask := (*sigset)(unsafe.Pointer(&_g_.m.sigmask))
201+
if unsafe.Sizeof(*smask) > unsafe.Sizeof(_g_.m.sigmask) {
202+
throw("insufficient storage for signal mask")
203+
}
204+
205+
// save current signal mask for muninit and unblock essential signals.
206+
var nset sigset
207+
copy(nset[:], sigActive[:])
208+
for i := range sigtable {
209+
if sigtable[i].flags&_SigUnblock != 0 {
210+
nset[(i-1)/32] |= 1 << ((uint32(i) - 1) & 31)
211+
}
212+
}
213+
rtsigprocmask(_SIG_UNBLOCK, &nset, smask, int32(unsafe.Sizeof(nset)))
200214
}
201215

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

@@ -304,6 +321,14 @@ func signalstack(p *byte, n int32) {
304321
sigaltstack(&st, nil)
305322
}
306323

324+
func unblocksig(i uint32) {
325+
var ss sigset
326+
if w := (i - 1) / 32; w < uint32(len(ss)) {
327+
ss[(i-1)/32] |= 1 << ((i - 1) & 31)
328+
rtsigprocmask(_SIG_UNBLOCK, &ss, nil, int32(unsafe.Sizeof(ss)))
329+
}
330+
}
331+
307332
func unblocksignals() {
308333
rtsigprocmask(_SIG_SETMASK, &sigset_none, nil, int32(unsafe.Sizeof(sigset_none)))
309334
}

src/runtime/os1_netbsd.go

Lines changed: 27 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -147,11 +147,29 @@ func minit() {
147147

148148
// Initialize signal handling
149149
signalstack((*byte)(unsafe.Pointer(_g_.m.gsignal.stack.lo)), 32*1024)
150-
sigprocmask(_SIG_SETMASK, &sigset_none, nil)
150+
151+
smask := (*sigset)(unsafe.Pointer(&_g_.m.sigmask))
152+
if unsafe.Sizeof(*smask) > unsafe.Sizeof(_g_.m.sigmask) {
153+
throw("insufficient storage for signal mask")
154+
}
155+
156+
// save current signal mask for muninit and unblock essential signals.
157+
var nset sigset
158+
copy(nset.__bits[:], sigActive[:])
159+
for i := range sigtable {
160+
if sigtable[i].flags&_SigUnblock != 0 {
161+
nset.__bits[(i-1)/32] |= 1 << ((uint32(i) - 1) & 31)
162+
}
163+
}
164+
sigprocmask(_SIG_UNBLOCK, &nset, smask)
151165
}
152166

153167
// Called from dropm to undo the effect of an minit.
154168
func unminit() {
169+
_g_ := getg()
170+
smask := (*sigset)(unsafe.Pointer(&_g_.m.sigmask))
171+
sigprocmask(_SIG_SETMASK, smask, nil)
172+
155173
signalstack(nil, 0)
156174
}
157175

@@ -206,6 +224,14 @@ func signalstack(p *byte, n int32) {
206224
sigaltstack(&st, nil)
207225
}
208226

227+
func unblocksig(i uint32) {
228+
var ss sigset
229+
if w := (i - 1) / 32; w < uint32(len(ss.__bits)) {
230+
ss.__bits[(i-1)/32] |= 1 << ((i - 1) & 31)
231+
sigprocmask(_SIG_UNBLOCK, &ss, nil)
232+
}
233+
}
234+
209235
func unblocksignals() {
210236
sigprocmask(_SIG_SETMASK, &sigset_none, nil)
211237
}

src/runtime/os1_openbsd.go

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -217,6 +217,10 @@ func signalstack(p *byte, n int32) {
217217
sigaltstack(&st, nil)
218218
}
219219

220+
func unblocksig(sig uint32) {
221+
// TODO(elias.naur): Implement when sigprocmask takes an oldset parameter
222+
}
223+
220224
func unblocksignals() {
221225
sigprocmask(_SIG_SETMASK, sigset_none)
222226
}

src/runtime/os2_linux.go

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@ const (
88
_SS_DISABLE = 2
99
_NSIG = 65
1010
_SI_USER = 0
11+
_SIG_UNBLOCK = 1
1112
_SIG_SETMASK = 2
1213
_RLIMIT_AS = 9
1314
)

src/runtime/os2_solaris.go

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@ package runtime
66

77
const (
88
_SS_DISABLE = 2
9+
_SIG_UNBLOCK = 2
910
_SIG_SETMASK = 3
1011
_NSIG = 73 /* number of signals in sigtable array */
1112
_SI_USER = 0

src/runtime/os3_solaris.go

Lines changed: 27 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -197,11 +197,29 @@ func minit() {
197197
asmcgocall(unsafe.Pointer(funcPC(miniterrno)), unsafe.Pointer(&libc____errno))
198198
// Initialize signal handling
199199
signalstack((*byte)(unsafe.Pointer(_g_.m.gsignal.stack.lo)), 32*1024)
200-
sigprocmask(_SIG_SETMASK, &sigset_none, nil)
200+
201+
smask := (*sigset)(unsafe.Pointer(&_g_.m.sigmask))
202+
if unsafe.Sizeof(*smask) > unsafe.Sizeof(_g_.m.sigmask) {
203+
throw("insufficient storage for signal mask")
204+
}
205+
206+
// save current signal mask for muninit and unblock essential signals.
207+
var nset sigset
208+
copy(nset.__sigbits[:], sigActive[:])
209+
for i := range sigtable {
210+
if sigtable[i].flags&_SigUnblock != 0 {
211+
nset.__sigbits[(i-1)/32] |= 1 << ((uint32(i) - 1) & 31)
212+
}
213+
}
214+
sigprocmask(_SIG_UNBLOCK, &nset, smask)
201215
}
202216

203217
// Called from dropm to undo the effect of an minit.
204218
func unminit() {
219+
_g_ := getg()
220+
smask := (*sigset)(unsafe.Pointer(&_g_.m.sigmask))
221+
sigprocmask(_SIG_SETMASK, smask, nil)
222+
205223
signalstack(nil, 0)
206224
}
207225

@@ -278,6 +296,14 @@ func signalstack(p *byte, n int32) {
278296
sigaltstack(&st, nil)
279297
}
280298

299+
func unblocksig(i uint32) {
300+
var ss sigset
301+
if w := (i - 1) / 32; w < uint32(len(ss.__sigbits)) {
302+
ss.__sigbits[(i-1)/32] |= 1 << ((i - 1) & 31)
303+
sigprocmask(_SIG_UNBLOCK, &ss, nil)
304+
}
305+
}
306+
281307
func unblocksignals() {
282308
sigprocmask(_SIG_SETMASK, &sigset_none, nil)
283309
}

src/runtime/runtime2.go

Lines changed: 11 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -266,6 +266,7 @@ type m struct {
266266
// Fields not known to debuggers.
267267
procid uint64 // for debuggers, but offset not hard-coded
268268
gsignal *g // signal-handling g
269+
sigmask [4]uintptr // storage for saved signal mask
269270
tls [4]uintptr // thread-local storage (for x86 extern register)
270271
mstartfn func()
271272
curg *g // current running goroutine
@@ -469,15 +470,16 @@ type sigtabtt struct {
469470
}
470471

471472
const (
472-
_SigNotify = 1 << 0 // let signal.Notify have signal, even if from kernel
473-
_SigKill = 1 << 1 // if signal.Notify doesn't take it, exit quietly
474-
_SigThrow = 1 << 2 // if signal.Notify doesn't take it, exit loudly
475-
_SigPanic = 1 << 3 // if the signal is from the kernel, panic
476-
_SigDefault = 1 << 4 // if the signal isn't explicitly requested, don't monitor it
477-
_SigHandling = 1 << 5 // our signal handler is registered
478-
_SigIgnored = 1 << 6 // the signal was ignored before we registered for it
479-
_SigGoExit = 1 << 7 // cause all runtime procs to exit (only used on Plan 9).
480-
_SigSetStack = 1 << 8 // add SA_ONSTACK to libc handler
473+
_SigNotify = 1 << iota // let signal.Notify have signal, even if from kernel
474+
_SigKill // if signal.Notify doesn't take it, exit quietly
475+
_SigThrow // if signal.Notify doesn't take it, exit loudly
476+
_SigPanic // if the signal is from the kernel, panic
477+
_SigDefault // if the signal isn't explicitly requested, don't monitor it
478+
_SigHandling // our signal handler is registered
479+
_SigIgnored // the signal was ignored before we registered for it
480+
_SigGoExit // cause all runtime procs to exit (only used on Plan 9).
481+
_SigSetStack // add SA_ONSTACK to libc handler
482+
_SigUnblock // unblocked in minit()
481483
)
482484

483485
// Layout of in-memory per-function information prepared by linker

0 commit comments

Comments
 (0)