diff options
| author | Michael Anthony Knyszek <mknyszek@google.com> | 2025-07-09 15:50:06 +0000 |
|---|---|---|
| committer | Michael Knyszek <mknyszek@google.com> | 2025-07-09 13:34:08 -0700 |
| commit | 8131635e5a9c7ae2fd2c083bed9e841d27226500 (patch) | |
| tree | 393d115b7bf91237e227828c5f5f9c44dda0ecd7 /src | |
| parent | 67c1704444ba9661699a80d0ea0a2df20b4107e2 (diff) | |
| download | go-8131635e5a9c7ae2fd2c083bed9e841d27226500.tar.xz | |
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 <golang-scoped@luci-project-accounts.iam.gserviceaccount.com>
Reviewed-by: Michael Pratt <mpratt@google.com>
Diffstat (limited to 'src')
| -rw-r--r-- | src/runtime/runtime-gdb_test.go | 3 | ||||
| -rw-r--r-- | src/runtime/testdata/testprognet/signalexec.go | 43 |
2 files changed, 46 insertions, 0 deletions
diff --git a/src/runtime/runtime-gdb_test.go b/src/runtime/runtime-gdb_test.go index 19ad29c127..47c1fe5851 100644 --- a/src/runtime/runtime-gdb_test.go +++ b/src/runtime/runtime-gdb_test.go @@ -78,6 +78,9 @@ func checkGdbVersion(t *testing.T) { if major < 10 { t.Skipf("skipping: gdb version %d.%d too old", major, minor) } + if major < 12 || (major == 12 && minor < 1) { + 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.") + } t.Logf("gdb version %d.%d", major, minor) } diff --git a/src/runtime/testdata/testprognet/signalexec.go b/src/runtime/testdata/testprognet/signalexec.go index 62ebce7176..7e7591e8f7 100644 --- a/src/runtime/testdata/testprognet/signalexec.go +++ b/src/runtime/testdata/testprognet/signalexec.go @@ -13,9 +13,11 @@ package main import ( "fmt" + "io" "os" "os/exec" "os/signal" + "runtime" "sync" "syscall" "time" @@ -23,10 +25,51 @@ import ( func init() { register("SignalDuringExec", SignalDuringExec) + register("SignalDuringExecPgrp", SignalDuringExecPgrp) register("Nop", Nop) } func SignalDuringExec() { + // Re-launch ourselves in a new process group. + cmd := exec.Command(os.Args[0], "SignalDuringExecPgrp") + cmd.Stdout = os.Stdout + cmd.Stderr = os.Stderr + cmd.SysProcAttr = &syscall.SysProcAttr{ + Setpgid: true, + } + + // Start the new process with an extra pipe. It will + // exit if the pipe is closed. + rp, wp, err := os.Pipe() + if err != nil { + fmt.Printf("Failed to create pipe: %v", err) + return + } + cmd.ExtraFiles = []*os.File{rp} + + // Run the command. + if err := cmd.Run(); err != nil { + fmt.Printf("Run failed: %v", err) + } + + // We don't actually need to write to the pipe, it just + // needs to get closed, which will happen on process + // exit. + runtime.KeepAlive(wp) +} + +func SignalDuringExecPgrp() { + // Grab fd 3 which is a pipe we need to read on. + f := os.NewFile(3, "pipe") + go func() { + // Nothing will ever get written to the pipe, so we'll + // just block on it. If it closes, ReadAll will return + // one way or another, at which point we'll exit. + io.ReadAll(f) + os.Exit(1) + }() + + // This is just for SignalDuringExec. pgrp := syscall.Getpgrp() const tries = 10 |
