]> Cypherpunks repositories - gostls13.git/commitdiff
runtime: run TestSignalDuringExec in its own process group
authorMichael Anthony Knyszek <mknyszek@google.com>
Wed, 9 Jul 2025 15:50:06 +0000 (15:50 +0000)
committerMichael Knyszek <mknyszek@google.com>
Wed, 9 Jul 2025 20:34:08 +0000 (13:34 -0700)
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>
src/runtime/runtime-gdb_test.go
src/runtime/testdata/testprognet/signalexec.go

index 19ad29c12717d277bc0189fa54efc5048847a15c..47c1fe58515cf26b2bfe0a83d47af36f19207d02 100644 (file)
@@ -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)
 }
 
index 62ebce7176f2206d34d7a3726f2a7347405a4ac0..7e7591e8f79a0add3cf8456806f93a4e8eed982b 100644 (file)
@@ -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