os/signal: completely ignore bogus signals

signal.Notify should ignore bogus signals (syscall.Signals with invalid
signal numbers). Ignoring means Notify/Stop/Reset all work fine, but the
channel will never receive any events.

Today, Stop hangs if Notify has only ever been called with bogus signals
because Stop assumes that runtime.signal_recv is running if a handler is
present. Notify unconditionally registers the handler, but only starts
signal_recv if the signal was valid.

Address this by avoiding registering the handler at all if the signal is
bogus.

We additionally add a bogus signal check to cancel (used by
Ignore/Reset). Currently those are calling into the runtime
signal_ignore and signal_disable, which do ignore bogus signals, but is
now inconsistent with the rest of the package.

For #77076.

Change-Id: I6a6a636c27c41a158e203bbf470be5f1f3f631bd
Reviewed-on: https://go-review.googlesource.com/c/go/+/735040
Reviewed-by: Cherry Mui <cherryyz@google.com>
LUCI-TryBot-Result: Go LUCI <golang-scoped@luci-project-accounts.iam.gserviceaccount.com>
Reviewed-by: Alex Brainman <alex.brainman@gmail.com>
Auto-Submit: Michael Pratt <mpratt@google.com>
This commit is contained in:
Michael Pratt
2026-01-08 18:36:45 -05:00
committed by Gopher Robot
parent 01b795bc4c
commit c2fabf1a26
3 changed files with 73 additions and 6 deletions

View File

@@ -54,6 +54,10 @@ func cancel(sigs []os.Signal, action func(int)) {
defer handlers.Unlock()
remove := func(n int) {
if n < 0 {
return
}
var zerohandler handler
for c, h := range handlers.m {
@@ -127,19 +131,27 @@ func Notify(c chan<- os.Signal, sig ...os.Signal) {
handlers.Lock()
defer handlers.Unlock()
h := handlers.m[c]
if h == nil {
if handlers.m == nil {
handlers.m = make(map[chan<- os.Signal]*handler)
// Lazily create the handler. If all of the signals are bogus there is
// no need to install a handler at all.
getHandler := func() *handler {
h := handlers.m[c]
if h == nil {
if handlers.m == nil {
handlers.m = make(map[chan<- os.Signal]*handler)
}
h = new(handler)
handlers.m[c] = h
}
h = new(handler)
handlers.m[c] = h
return h
}
add := func(n int) {
if n < 0 {
return
}
h := getHandler()
if !h.want(n) {
h.set(n)
if handlers.ref[n] == 0 {

View File

@@ -0,0 +1,26 @@
// Copyright 2026 The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
package runtime_test
import (
"runtime"
"testing"
)
func TestSignalBogus(t *testing.T) {
if runtime.GOOS == "plan9" {
t.Skip("No syscall.Signal on plan9")
}
// This test more properly belongs in os/signal, but it depends on
// containing the only call to signal.Notify in the process, so it must
// run as an isolated subprocess, which is simplest with testprog.
t.Parallel()
output := runTestProg(t, "testprog", "SignalBogus")
want := "OK\n"
if output != want {
t.Fatalf("output is not %q\n%s", want, output)
}
}

View File

@@ -0,0 +1,29 @@
// Copyright 2026 The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
//go:build !plan9
package main
import (
"os"
"os/signal"
"syscall"
)
func init() {
register("SignalBogus", SignalBogus)
}
// signal.Notify should effectively ignore bogus signal numbers. Never writing
// to the channel, but otherwise allowing Notify/Stop as normal.
//
// This is a regression test for https://go.dev/issue/77076, where bogus
// signals used to make Stop hang if there were no real signals installed.
func SignalBogus() {
ch := make(chan os.Signal, 1)
signal.Notify(ch, syscall.Signal(0xdead))
signal.Stop(ch)
println("OK")
}