From 3fa54a93ebbfbb5674df145adcb3eb5b4e781c91 Mon Sep 17 00:00:00 2001 From: Fabian Meumertzheim <fabian@meumertzhe.im> Date: Thu, 13 Feb 2025 08:33:50 +0100 Subject: [PATCH 1/3] Add test --- src/os/signal/signal_test.go | 117 ++++++++++++++++++++++++++++++++++- 1 file changed, 114 insertions(+), 3 deletions(-) diff --git a/src/os/signal/signal_test.go b/src/os/signal/signal_test.go index d54787bc199a3b..d70127e43b0e03 100644 --- a/src/os/signal/signal_test.go +++ b/src/os/signal/signal_test.go @@ -217,9 +217,7 @@ func testCancel(t *testing.T, ignore bool) { // Either way, this should undo both calls to Notify above. if ignore { Ignore(syscall.SIGWINCH, syscall.SIGHUP) - // Don't bother deferring a call to Reset: it is documented to undo Notify, - // but its documentation says nothing about Ignore, and (as of the time of - // writing) it empirically does not undo an Ignore. + defer Reset(syscall.SIGWINCH, syscall.SIGHUP) } else { Reset(syscall.SIGWINCH, syscall.SIGHUP) } @@ -349,6 +347,8 @@ func TestStop(t *testing.T) { for _, sig := range sigs { sig := sig t.Run(fmt.Sprint(sig), func(t *testing.T) { + defer Reset(sig) + // When calling Notify with a specific signal, // independent signals should not interfere with each other, // and we end up needing to wait for signals to quiesce a lot. @@ -913,3 +913,114 @@ func TestSignalTrace(t *testing.T) { close(quit) <-done } + +// #46321 test Reset actually undoes the effect of Ignore. +func TestResetIgnore(t *testing.T) { + if os.Getenv("GO_TEST_RESET_IGNORE") != "" { + s, err := strconv.Atoi(os.Getenv("GO_TEST_RESET_IGNORE")) + if err != nil { + t.Fatalf("failed to parse signal: %v", err) + } + resetIgnoreTestProgram(syscall.Signal(s)) + } + + sigs := []syscall.Signal{ + syscall.SIGHUP, + syscall.SIGINT, + syscall.SIGUSR1, + syscall.SIGTERM, + syscall.SIGCHLD, + syscall.SIGWINCH, + } + + for _, notify := range []bool{false, true} { + for _, sig := range sigs { + t.Run(fmt.Sprintf("%s[notify=%t]", sig, notify), func(t *testing.T) { + if Ignored(sig) { + t.Fatalf("expected %q to not be ignored initially", sig) + } + + Ignore(sig) + if notify { + c := make(chan os.Signal, 1) + Notify(c, sig) + defer Stop(c) + } + Reset(sig) + + if Ignored(sig) { + t.Fatalf("expected %q to not be ignored", sig) + } + + // Child processes inherit the ignored status of signals, so verify that it + // is indeed not ignored. + cmd := testenv.Command(t, testenv.Executable(t), "-test.run=^TestResetIgnore$") + cmd.Env = append(os.Environ(), "GO_TEST_RESET_IGNORE="+strconv.Itoa(int(sig))) + err := cmd.Run() + if _, ok := err.(*exec.ExitError); ok { + t.Fatalf("expected %q to not be ignored in child process", sig) + } else if err != nil { + t.Fatalf("child process failed to launch: %v", err) + } + }) + } + } +} + +func resetIgnoreTestProgram(sig os.Signal) { + if Ignored(sig) { + os.Exit(1) + } + os.Exit(0) +} + +// #46321 test Reset correctly undoes the effect of Ignore when the child +// process is started with a signal ignored. +func TestInitiallyIgnoredResetIgnore(t *testing.T) { + testenv.MustHaveExec(t) + + if os.Getenv("GO_TEST_INITIALLY_IGNORED_RESET_IGNORE") != "" { + s, err := strconv.Atoi(os.Getenv("GO_TEST_INITIALLY_IGNORED_RESET_IGNORE")) + if err != nil { + t.Fatalf("failed to parse signal: %v", err) + } + initiallyIgnoredResetIgnoreTestProgram(syscall.Signal(s)) + } + + sigs := []syscall.Signal{ + syscall.SIGINT, + syscall.SIGHUP, + } + + for _, sig := range sigs { + t.Run(fmt.Sprint(sig), func(t *testing.T) { + Ignore(sig) + defer Reset(sig) + + cmd := testenv.Command(t, testenv.Executable(t), "-test.run=^TestInitiallyIgnoredResetIgnore$") + cmd.Env = append(os.Environ(), "GO_TEST_INITIALLY_IGNORED_RESET_IGNORE="+strconv.Itoa(int(sig))) + err := cmd.Run() + if _, ok := err.(*exec.ExitError); ok { + t.Fatalf("expected %q to be ignored in child process", sig) + } else if err != nil { + t.Fatalf("child process failed to launch: %v", err) + } + }) + } +} + +func initiallyIgnoredResetIgnoreTestProgram(sig os.Signal) { + if !Ignored(sig) { + os.Exit(1) + } + Reset(sig) + if !Ignored(sig) { + os.Exit(1) + } + Ignore(sig) + Reset(sig) + if !Ignored(sig) { + os.Exit(1) + } + os.Exit(0) +} From a1cecf9e1bc0497dd979f090076bce3a72c13d1d Mon Sep 17 00:00:00 2001 From: Fabian Meumertzheim <fabian@meumertzhe.im> Date: Thu, 13 Feb 2025 08:33:50 +0100 Subject: [PATCH 2/3] Fix test --- src/os/signal/signal.go | 4 ++-- src/runtime/os3_plan9.go | 3 ++- src/runtime/os_wasm.go | 2 +- src/runtime/signal_unix.go | 38 ++++++++++++++++++++++++++--------- src/runtime/signal_windows.go | 3 ++- src/runtime/sigqueue.go | 10 ++++++++- 6 files changed, 44 insertions(+), 16 deletions(-) diff --git a/src/os/signal/signal.go b/src/os/signal/signal.go index b9fe16baa50b1f..81b4dbbcce3f91 100644 --- a/src/os/signal/signal.go +++ b/src/os/signal/signal.go @@ -168,8 +168,8 @@ func Notify(c chan<- os.Signal, sig ...os.Signal) { } } -// Reset undoes the effect of any prior calls to [Notify] for the provided -// signals. +// Reset undoes the effect of any prior calls to [Notify] or [Ignore] for the +// provided signals. // If no signals are provided, all signal handlers will be reset. func Reset(sig ...os.Signal) { cancel(sig, disableSignal) diff --git a/src/runtime/os3_plan9.go b/src/runtime/os3_plan9.go index dd1570561803f8..efc5a33af71764 100644 --- a/src/runtime/os3_plan9.go +++ b/src/runtime/os3_plan9.go @@ -149,7 +149,8 @@ Exit: func sigenable(sig uint32) { } -func sigdisable(sig uint32) { +func sigdisable(sig uint32) bool { + return false } func sigignore(sig uint32) { diff --git a/src/runtime/os_wasm.go b/src/runtime/os_wasm.go index 8046caf45ef133..296e0fd860f127 100644 --- a/src/runtime/os_wasm.go +++ b/src/runtime/os_wasm.go @@ -142,7 +142,7 @@ func getfp() uintptr { return 0 } func setProcessCPUProfiler(hz int32) {} func setThreadCPUProfiler(hz int32) {} -func sigdisable(uint32) {} +func sigdisable(uint32) bool { return false } func sigenable(uint32) {} func sigignore(uint32) {} diff --git a/src/runtime/signal_unix.go b/src/runtime/signal_unix.go index 96628d6baae910..8ac2724332a096 100644 --- a/src/runtime/signal_unix.go +++ b/src/runtime/signal_unix.go @@ -202,23 +202,28 @@ func sigenable(sig uint32) { enableSigChan <- sig <-maskUpdatedChan if atomic.Cas(&handlingSig[sig], 0, 1) { - atomic.Storeuintptr(&fwdSig[sig], getsig(sig)) + h := getsig(sig) + if h != _SIG_IGN { + atomic.Storeuintptr(&fwdSig[sig], h) + } setsig(sig, abi.FuncPCABIInternal(sighandler)) } } } -// sigdisable disables the Go signal handler for the signal sig. +// sigdisable resets the signal handler for the signal sig back to the default +// at program startup or the last custom handler registered by cgo. // It is only called while holding the os/signal.handlers lock, // via os/signal.disableSignal and signal_disable. -func sigdisable(sig uint32) { +// Returns true if the signal is ignored after the change. +func sigdisable(sig uint32) bool { if sig >= uint32(len(sigtable)) { - return + return false } // SIGPROF is handled specially for profiling. if sig == _SIGPROF { - return + return false } t := &sigtable[sig] @@ -227,14 +232,27 @@ func sigdisable(sig uint32) { disableSigChan <- sig <-maskUpdatedChan - // If initsig does not install a signal handler for a - // signal, then to go back to the state before Notify - // we should remove the one we installed. - if !sigInstallGoHandler(sig) { + if sigInstallGoHandler(sig) { + if atomic.Cas(&handlingSig[sig], 0, 1) { + h := getsig(sig) + // Preserve custom signal handlers installed with cgo. + if h != _SIG_IGN && h != _SIG_DFL { + atomic.Storeuintptr(&fwdSig[sig], h) + } + setsig(sig, abi.FuncPCABIInternal(sighandler)) + } + return false + } else { + // If initsig does not install a signal handler for a + // signal, then to go back to the state before Notify + // we should remove the one we installed. atomic.Store(&handlingSig[sig], 0) - setsig(sig, atomic.Loaduintptr(&fwdSig[sig])) + fs := atomic.Loaduintptr(&fwdSig[sig]) + setsig(sig, fs) + return fs == _SIG_IGN } } + return false } // sigignore ignores the signal sig. diff --git a/src/runtime/signal_windows.go b/src/runtime/signal_windows.go index b0c653ee46dd0b..67d68476513a3f 100644 --- a/src/runtime/signal_windows.go +++ b/src/runtime/signal_windows.go @@ -434,7 +434,8 @@ func initsig(preinit bool) { func sigenable(sig uint32) { } -func sigdisable(sig uint32) { +func sigdisable(sig uint32) bool { + return false } func sigignore(sig uint32) { diff --git a/src/runtime/sigqueue.go b/src/runtime/sigqueue.go index 62a8e8a70226fe..299e5ee87e28ef 100644 --- a/src/runtime/sigqueue.go +++ b/src/runtime/sigqueue.go @@ -230,11 +230,19 @@ func signal_disable(s uint32) { if s >= uint32(len(sig.wanted)*32) { return } - sigdisable(s) + ignored := sigdisable(s) w := sig.wanted[s/32] w &^= 1 << (s & 31) atomic.Store(&sig.wanted[s/32], w) + + i := sig.ignored[s/32] + if ignored { + i |= 1 << (s & 31) + } else { + i &^= 1 << (s & 31) + } + atomic.Store(&sig.ignored[s/32], i) } // Must only be called from a single goroutine at a time. From 6dd5b0dbda6b7cdcbc971d5e4fc4f96403661498 Mon Sep 17 00:00:00 2001 From: Fabian Meumertzheim <fabian@meumertzhe.im> Date: Thu, 20 Feb 2025 22:37:49 +0100 Subject: [PATCH 3/3] Address first round of comments --- src/os/signal/signal_test.go | 26 +++++++++----------------- src/runtime/signal_unix.go | 2 +- 2 files changed, 10 insertions(+), 18 deletions(-) diff --git a/src/os/signal/signal_test.go b/src/os/signal/signal_test.go index d70127e43b0e03..d56866723489d8 100644 --- a/src/os/signal/signal_test.go +++ b/src/os/signal/signal_test.go @@ -921,7 +921,10 @@ func TestResetIgnore(t *testing.T) { if err != nil { t.Fatalf("failed to parse signal: %v", err) } - resetIgnoreTestProgram(syscall.Signal(s)) + if Ignored(syscall.Signal(s)) { + os.Exit(1) + } + os.Exit(0) } sigs := []syscall.Signal{ @@ -937,7 +940,7 @@ func TestResetIgnore(t *testing.T) { for _, sig := range sigs { t.Run(fmt.Sprintf("%s[notify=%t]", sig, notify), func(t *testing.T) { if Ignored(sig) { - t.Fatalf("expected %q to not be ignored initially", sig) + t.Skipf("expected %q to not be ignored initially", sig) } Ignore(sig) @@ -957,23 +960,14 @@ func TestResetIgnore(t *testing.T) { cmd := testenv.Command(t, testenv.Executable(t), "-test.run=^TestResetIgnore$") cmd.Env = append(os.Environ(), "GO_TEST_RESET_IGNORE="+strconv.Itoa(int(sig))) err := cmd.Run() - if _, ok := err.(*exec.ExitError); ok { - t.Fatalf("expected %q to not be ignored in child process", sig) - } else if err != nil { - t.Fatalf("child process failed to launch: %v", err) + if err != nil { + t.Fatalf("expected %q to not be ignored in child process: %v", sig, err) } }) } } } -func resetIgnoreTestProgram(sig os.Signal) { - if Ignored(sig) { - os.Exit(1) - } - os.Exit(0) -} - // #46321 test Reset correctly undoes the effect of Ignore when the child // process is started with a signal ignored. func TestInitiallyIgnoredResetIgnore(t *testing.T) { @@ -1000,10 +994,8 @@ func TestInitiallyIgnoredResetIgnore(t *testing.T) { cmd := testenv.Command(t, testenv.Executable(t), "-test.run=^TestInitiallyIgnoredResetIgnore$") cmd.Env = append(os.Environ(), "GO_TEST_INITIALLY_IGNORED_RESET_IGNORE="+strconv.Itoa(int(sig))) err := cmd.Run() - if _, ok := err.(*exec.ExitError); ok { - t.Fatalf("expected %q to be ignored in child process", sig) - } else if err != nil { - t.Fatalf("child process failed to launch: %v", err) + if err != nil { + t.Fatalf("expected %q to be ignored in child process: %v", sig, err) } }) } diff --git a/src/runtime/signal_unix.go b/src/runtime/signal_unix.go index 8ac2724332a096..c8dcdbc87a1d9f 100644 --- a/src/runtime/signal_unix.go +++ b/src/runtime/signal_unix.go @@ -215,7 +215,7 @@ func sigenable(sig uint32) { // at program startup or the last custom handler registered by cgo. // It is only called while holding the os/signal.handlers lock, // via os/signal.disableSignal and signal_disable. -// Returns true if the signal is ignored after the change. +// Reports whether the signal is ignored after the change. func sigdisable(sig uint32) bool { if sig >= uint32(len(sigtable)) { return false