Skip to content

Commit 78074f6

Browse files
author
Elias Naur
committed
runtime: handle SIGPIPE in c-archive and c-shared programs
Before this CL, Go programs in c-archive or c-shared buildmodes would not handle SIGPIPE. That leads to surprising behaviour where writes on a closed pipe or socket would raise SIGPIPE and terminate the program. This CL changes the Go runtime to handle SIGPIPE regardless of buildmode. In addition, SIGPIPE from non-Go code is forwarded. This is a refinement of CL 32796 that fixes the case where a non-default handler for SIGPIPE is installed by the host C program. Fixes #17393 Change-Id: Ia41186e52c1ac209d0a594bae9904166ae7df7de Reviewed-on: https://go-review.googlesource.com/35960 Run-TryBot: Elias Naur <[email protected]> TryBot-Result: Gobot Gobot <[email protected]> Reviewed-by: Ian Lance Taylor <[email protected]>
1 parent b612ab3 commit 78074f6

File tree

10 files changed

+165
-11
lines changed

10 files changed

+165
-11
lines changed

misc/cgo/testcarchive/carchive_test.go

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -265,6 +265,25 @@ func TestSignalForwarding(t *testing.T) {
265265
t.Logf("%s", out)
266266
t.Errorf("got %v; expected SIGSEGV", ee)
267267
}
268+
269+
// Test SIGPIPE forwarding
270+
cmd = exec.Command(bin[0], append(bin[1:], "3")...)
271+
272+
out, err = cmd.CombinedOutput()
273+
274+
if err == nil {
275+
t.Logf("%s", out)
276+
t.Error("test program succeeded unexpectedly")
277+
} else if ee, ok := err.(*exec.ExitError); !ok {
278+
t.Logf("%s", out)
279+
t.Errorf("error (%v) has type %T; expected exec.ExitError", err, err)
280+
} else if ws, ok := ee.Sys().(syscall.WaitStatus); !ok {
281+
t.Logf("%s", out)
282+
t.Errorf("error.Sys (%v) has type %T; expected syscall.WaitStatus", ee.Sys(), ee.Sys())
283+
} else if !ws.Signaled() || ws.Signal() != syscall.SIGPIPE {
284+
t.Logf("%s", out)
285+
t.Errorf("got %v; expected SIGPIPE", ee)
286+
}
268287
}
269288

270289
func TestSignalForwardingExternal(t *testing.T) {

misc/cgo/testcarchive/main2.c

Lines changed: 34 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@
1717
#include <unistd.h>
1818
#include <sched.h>
1919
#include <time.h>
20+
#include <errno.h>
2021

2122
#include "libgo2.h"
2223

@@ -26,6 +27,7 @@ static void die(const char* msg) {
2627
}
2728

2829
static volatile sig_atomic_t sigioSeen;
30+
static volatile sig_atomic_t sigpipeSeen;
2931

3032
// Use up some stack space.
3133
static void recur(int i, char *p) {
@@ -37,6 +39,10 @@ static void recur(int i, char *p) {
3739
}
3840
}
3941

42+
static void pipeHandler(int signo, siginfo_t* info, void* ctxt) {
43+
sigpipeSeen = 1;
44+
}
45+
4046
// Signal handler that uses up more stack space than a goroutine will have.
4147
static void ioHandler(int signo, siginfo_t* info, void* ctxt) {
4248
char a[1024];
@@ -106,6 +112,10 @@ static void init() {
106112
die("sigaction");
107113
}
108114

115+
sa.sa_sigaction = pipeHandler;
116+
if (sigaction(SIGPIPE, &sa, NULL) < 0) {
117+
die("sigaction");
118+
}
109119
}
110120

111121
int main(int argc, char** argv) {
@@ -167,7 +177,30 @@ int main(int argc, char** argv) {
167177
nanosleep(&ts, NULL);
168178
i++;
169179
if (i > 5000) {
170-
fprintf(stderr, "looping too long waiting for signal\n");
180+
fprintf(stderr, "looping too long waiting for SIGIO\n");
181+
exit(EXIT_FAILURE);
182+
}
183+
}
184+
185+
if (verbose) {
186+
printf("provoking SIGPIPE\n");
187+
}
188+
189+
GoRaiseSIGPIPE();
190+
191+
if (verbose) {
192+
printf("waiting for sigpipeSeen\n");
193+
}
194+
195+
// Wait until the signal has been delivered.
196+
i = 0;
197+
while (!sigpipeSeen) {
198+
ts.tv_sec = 0;
199+
ts.tv_nsec = 1000000;
200+
nanosleep(&ts, NULL);
201+
i++;
202+
if (i > 5000) {
203+
fprintf(stderr, "looping too long waiting for SIGPIPE\n");
171204
exit(EXIT_FAILURE);
172205
}
173206
}

misc/cgo/testcarchive/main3.c

Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,31 @@ static void ioHandler(int signo, siginfo_t* info, void* ctxt) {
2525
sigioSeen = 1;
2626
}
2727

28+
// Set up the SIGPIPE signal handler in a high priority constructor, so
29+
// that it is installed before the Go code starts.
30+
31+
static void pipeHandler(int signo, siginfo_t* info, void* ctxt) {
32+
const char *s = "unexpected SIGPIPE\n";
33+
write(2, s, strlen(s));
34+
exit(EXIT_FAILURE);
35+
}
36+
37+
static void init(void) __attribute__ ((constructor (200)));
38+
39+
static void init() {
40+
struct sigaction sa;
41+
42+
memset(&sa, 0, sizeof sa);
43+
sa.sa_sigaction = pipeHandler;
44+
if (sigemptyset(&sa.sa_mask) < 0) {
45+
die("sigemptyset");
46+
}
47+
sa.sa_flags = SA_SIGINFO;
48+
if (sigaction(SIGPIPE, &sa, NULL) < 0) {
49+
die("sigaction");
50+
}
51+
}
52+
2853
int main(int argc, char** argv) {
2954
int verbose;
3055
struct sigaction sa;
@@ -34,6 +59,14 @@ int main(int argc, char** argv) {
3459
verbose = argc > 2;
3560
setvbuf(stdout, NULL, _IONBF, 0);
3661

62+
if (verbose) {
63+
printf("raising SIGPIPE\n");
64+
}
65+
66+
// Test that the Go runtime handles SIGPIPE, even if we installed
67+
// a non-default SIGPIPE handler before the runtime initializes.
68+
ProvokeSIGPIPE();
69+
3770
if (verbose) {
3871
printf("calling sigaction\n");
3972
}

misc/cgo/testcarchive/main5.c

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -68,6 +68,24 @@ int main(int argc, char** argv) {
6868

6969
break;
7070
}
71+
case 3: {
72+
if (verbose) {
73+
printf("attempting SIGPIPE\n");
74+
}
75+
76+
int fd[2];
77+
if (pipe(fd) != 0) {
78+
printf("pipe(2) failed\n");
79+
return 0;
80+
}
81+
// Close the reading end.
82+
close(fd[0]);
83+
// Expect that write(2) fails (EPIPE)
84+
if (write(fd[1], "some data", 9) != -1) {
85+
printf("write(2) unexpectedly succeeded\n");
86+
return 0;
87+
}
88+
}
7189
default:
7290
printf("Unknown test: %d\n", test);
7391
return 0;

misc/cgo/testcarchive/src/libgo2/libgo2.go

Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,30 @@
44

55
package main
66

7+
/*
8+
#include <signal.h>
9+
#include <unistd.h>
10+
#include <stdlib.h>
11+
#include <stdio.h>
12+
13+
// Raise SIGPIPE.
14+
static void CRaiseSIGPIPE() {
15+
int fds[2];
16+
17+
if (pipe(fds) == -1) {
18+
perror("pipe");
19+
exit(EXIT_FAILURE);
20+
}
21+
// Close the reader end
22+
close(fds[0]);
23+
// Write to the writer end to provoke a SIGPIPE
24+
if (write(fds[1], "some data", 9) != -1) {
25+
fprintf(stderr, "write to a closed pipe succeeded\n");
26+
exit(EXIT_FAILURE);
27+
}
28+
close(fds[1]);
29+
}
30+
*/
731
import "C"
832

933
import (
@@ -46,5 +70,11 @@ func TestSEGV() {
4670
func Noop() {
4771
}
4872

73+
// Raise SIGPIPE.
74+
//export GoRaiseSIGPIPE
75+
func GoRaiseSIGPIPE() {
76+
C.CRaiseSIGPIPE()
77+
}
78+
4979
func main() {
5080
}

misc/cgo/testcarchive/src/libgo3/libgo3.go

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -40,5 +40,17 @@ func SawSIGIO() C.int {
4040
}
4141
}
4242

43+
// ProvokeSIGPIPE provokes a kernel-initiated SIGPIPE.
44+
//export ProvokeSIGPIPE
45+
func ProvokeSIGPIPE() {
46+
r, w, err := os.Pipe()
47+
if err != nil {
48+
panic(err)
49+
}
50+
r.Close()
51+
defer w.Close()
52+
w.Write([]byte("some data"))
53+
}
54+
4355
func main() {
4456
}

src/os/signal/doc.go

Lines changed: 5 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -181,10 +181,11 @@ If the Go runtime sees an existing signal handler for the SIGCANCEL or
181181
SIGSETXID signals (which are used only on GNU/Linux), it will turn on
182182
the SA_ONSTACK flag and otherwise keep the signal handler.
183183
184-
For the synchronous signals, the Go runtime will install a signal
185-
handler. It will save any existing signal handler. If a synchronous
186-
signal arrives while executing non-Go code, the Go runtime will invoke
187-
the existing signal handler instead of the Go signal handler.
184+
For the synchronous signals and SIGPIPE, the Go runtime will install a
185+
signal handler. It will save any existing signal handler. If a
186+
synchronous signal arrives while executing non-Go code, the Go runtime
187+
will invoke the existing signal handler instead of the Go signal
188+
handler.
188189
189190
Go code built with -buildmode=c-archive or -buildmode=c-shared will
190191
not install any other signal handlers by default. If there is an

src/runtime/cgocall.go

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -110,6 +110,7 @@ func cgocall(fn, arg unsafe.Pointer) int32 {
110110
mp := getg().m
111111
mp.ncgocall++
112112
mp.ncgo++
113+
mp.incgo = true
113114

114115
// Reset traceback.
115116
mp.cgoCallers[0] = 0
@@ -151,6 +152,7 @@ func cgocall(fn, arg unsafe.Pointer) int32 {
151152

152153
//go:nosplit
153154
func endcgo(mp *m) {
155+
mp.incgo = false
154156
mp.ncgo--
155157

156158
if raceenabled {
@@ -180,9 +182,11 @@ func cgocallbackg(ctxt uintptr) {
180182
savedsp := unsafe.Pointer(gp.syscallsp)
181183
savedpc := gp.syscallpc
182184
exitsyscall(0) // coming out of cgo call
185+
gp.m.incgo = false
183186

184187
cgocallbackg1(ctxt)
185188

189+
gp.m.incgo = true
186190
// going back to cgo call
187191
reentersyscall(savedpc, uintptr(savedsp))
188192

src/runtime/runtime2.go

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -429,6 +429,7 @@ type m struct {
429429
inwb bool // m is executing a write barrier
430430
newSigstack bool // minit on C thread called sigaltstack
431431
printlock int8
432+
incgo bool // m is executing a cgo call
432433
fastrand uint32
433434
ncgocall uint64 // number of cgo calls in total
434435
ncgo int32 // number of cgo calls currently in progress

src/runtime/signal_unix.go

Lines changed: 9 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -111,8 +111,8 @@ func sigInstallGoHandler(sig uint32) bool {
111111
}
112112

113113
// When built using c-archive or c-shared, only install signal
114-
// handlers for synchronous signals.
115-
if (isarchive || islibrary) && t.flags&_SigPanic == 0 {
114+
// handlers for synchronous signals and SIGPIPE.
115+
if (isarchive || islibrary) && t.flags&_SigPanic == 0 && sig != _SIGPIPE {
116116
return false
117117
}
118118

@@ -518,16 +518,19 @@ func sigfwdgo(sig uint32, info *siginfo, ctx unsafe.Pointer) bool {
518518
return true
519519
}
520520

521-
// Only forward synchronous signals.
522521
c := &sigctxt{info, ctx}
523-
if c.sigcode() == _SI_USER || flags&_SigPanic == 0 {
522+
// Only forward synchronous signals and SIGPIPE.
523+
// Unfortunately, user generated SIGPIPEs will also be forwarded, because si_code
524+
// is set to _SI_USER even for a SIGPIPE raised from a write to a closed socket
525+
// or pipe.
526+
if (c.sigcode() == _SI_USER || flags&_SigPanic == 0) && sig != _SIGPIPE {
524527
return false
525528
}
526529
// Determine if the signal occurred inside Go code. We test that:
527530
// (1) we were in a goroutine (i.e., m.curg != nil), and
528-
// (2) we weren't in CGO (i.e., m.curg.syscallsp == 0).
531+
// (2) we weren't in CGO.
529532
g := getg()
530-
if g != nil && g.m != nil && g.m.curg != nil && g.m.curg.syscallsp == 0 {
533+
if g != nil && g.m != nil && g.m.curg != nil && !g.m.incgo {
531534
return false
532535
}
533536
// Signal not handled by Go, forward it.

0 commit comments

Comments
 (0)