Skip to content

Commit 8131635

Browse files
committed
runtime: run TestSignalDuringExec in its own process group
TestSignalDuringExec sends a SIGWINCH to the whole process group. However, it may execute concurrently with other copies of the runtime tests, especially through `go tool dist`, and gdb version <12.1 has a bug in non-interactive mode where recieving a SIGWINCH causes a crash. This change modifies SignalDuringExec in the testprog to first fork itself into a new process group. To avoid issues with Ctrl+C and the new process group hanging, the new process blocks on a pipe that is passed down to it. This pipe is automatically closed when its parent exits, which should ensure that the subprocess also exits. Fixes #58932. Change-Id: I3906afa28cf8b15d22ae612d071bce7f30fc3e6c Cq-Include-Trybots: luci.golang.try:gotip-linux-amd64-longtest-noswissmap,gotip-linux-amd64-longtest-aliastypeparams,gotip-linux-amd64-longtest,gotip-linux-386-longtest Reviewed-on: https://go-review.googlesource.com/c/go/+/686875 LUCI-TryBot-Result: Go LUCI <[email protected]> Reviewed-by: Michael Pratt <[email protected]>
1 parent 67c1704 commit 8131635

File tree

2 files changed

+46
-0
lines changed

2 files changed

+46
-0
lines changed

src/runtime/runtime-gdb_test.go

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -78,6 +78,9 @@ func checkGdbVersion(t *testing.T) {
7878
if major < 10 {
7979
t.Skipf("skipping: gdb version %d.%d too old", major, minor)
8080
}
81+
if major < 12 || (major == 12 && minor < 1) {
82+
t.Logf("gdb version <12.1 is known to crash due to a SIGWINCH recieved in non-interactive mode; if you see a crash, some test may be sending SIGWINCH to the whole process group. See go.dev/issue/58932.")
83+
}
8184
t.Logf("gdb version %d.%d", major, minor)
8285
}
8386

src/runtime/testdata/testprognet/signalexec.go

Lines changed: 43 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,20 +13,63 @@ package main
1313

1414
import (
1515
"fmt"
16+
"io"
1617
"os"
1718
"os/exec"
1819
"os/signal"
20+
"runtime"
1921
"sync"
2022
"syscall"
2123
"time"
2224
)
2325

2426
func init() {
2527
register("SignalDuringExec", SignalDuringExec)
28+
register("SignalDuringExecPgrp", SignalDuringExecPgrp)
2629
register("Nop", Nop)
2730
}
2831

2932
func SignalDuringExec() {
33+
// Re-launch ourselves in a new process group.
34+
cmd := exec.Command(os.Args[0], "SignalDuringExecPgrp")
35+
cmd.Stdout = os.Stdout
36+
cmd.Stderr = os.Stderr
37+
cmd.SysProcAttr = &syscall.SysProcAttr{
38+
Setpgid: true,
39+
}
40+
41+
// Start the new process with an extra pipe. It will
42+
// exit if the pipe is closed.
43+
rp, wp, err := os.Pipe()
44+
if err != nil {
45+
fmt.Printf("Failed to create pipe: %v", err)
46+
return
47+
}
48+
cmd.ExtraFiles = []*os.File{rp}
49+
50+
// Run the command.
51+
if err := cmd.Run(); err != nil {
52+
fmt.Printf("Run failed: %v", err)
53+
}
54+
55+
// We don't actually need to write to the pipe, it just
56+
// needs to get closed, which will happen on process
57+
// exit.
58+
runtime.KeepAlive(wp)
59+
}
60+
61+
func SignalDuringExecPgrp() {
62+
// Grab fd 3 which is a pipe we need to read on.
63+
f := os.NewFile(3, "pipe")
64+
go func() {
65+
// Nothing will ever get written to the pipe, so we'll
66+
// just block on it. If it closes, ReadAll will return
67+
// one way or another, at which point we'll exit.
68+
io.ReadAll(f)
69+
os.Exit(1)
70+
}()
71+
72+
// This is just for SignalDuringExec.
3073
pgrp := syscall.Getpgrp()
3174

3275
const tries = 10

0 commit comments

Comments
 (0)