From ff13b738570e3299f075bb14efa63ebe8cb265f2 Mon Sep 17 00:00:00 2001 From: Evan Wies Date: Thu, 27 Aug 2026 17:32:32 -0400 Subject: [PATCH] runtime: block signal_recv on targets without signal delivery The signal_recv stub returns immediately. After signal.Notify starts the signal goroutine, os/signal.loop calls the stub continuously. On cooperative schedulers, this prevents other goroutines from running. On js/wasm, it also prevents control from returning to the host. Call deadlock() to block the signal goroutine permanently. These targets cannot deliver signals. Add a regression test that calls signal.Notify and then sleeps. The test checks that the main goroutine can resume and exit. Skip this test on Windows, which has no signal implementation. Fixes #5619 Signed-off-by: Evan Wies --- main_test.go | 10 +++++++++- src/runtime/signalstub.go | 7 ++++++- testdata/signalnotify.go | 14 ++++++++++++++ testdata/signalnotify.txt | 1 + 4 files changed, 30 insertions(+), 2 deletions(-) create mode 100644 testdata/signalnotify.go create mode 100644 testdata/signalnotify.txt diff --git a/main_test.go b/main_test.go index ae4d7406a7..a13d42803c 100644 --- a/main_test.go +++ b/main_test.go @@ -81,6 +81,7 @@ func TestBuild(t *testing.T) { "print.go", "reflect.go", "signal.go", + "signalnotify.go", "slice.go", "sort.go", "stdlib.go", @@ -440,7 +441,14 @@ func runPlatTests(options compileopts.Options, tests []string, t *testing.T) { continue } } - if isWebAssembly || isBaremetal || options.GOOS == "windows" { + if options.GOOS == "windows" { + switch name { + case "signal.go", "signalnotify.go": + // os/signal does not link on Windows. + continue + } + } + if isWebAssembly || isBaremetal { switch name { case "signal.go": // Signals only work on POSIX-like systems. diff --git a/src/runtime/signalstub.go b/src/runtime/signalstub.go index fd7d2606a9..995255c192 100644 --- a/src/runtime/signalstub.go +++ b/src/runtime/signalstub.go @@ -18,4 +18,9 @@ func signal_ignore(uint32) {} func signal_waitUntilIdle() {} //go:linkname signal_recv os/signal.signal_recv -func signal_recv() uint32 { return ^uint32(0) } +func signal_recv() uint32 { + // Block to prevent os/signal.loop from running continuously. + // See https://go.dev/src/os/signal/signal_unix.go. + deadlock() + return 0 +} diff --git a/testdata/signalnotify.go b/testdata/signalnotify.go new file mode 100644 index 0000000000..ae8f7a23fe --- /dev/null +++ b/testdata/signalnotify.go @@ -0,0 +1,14 @@ +package main + +import ( + "os" + "os/signal" + "time" +) + +func main() { + c := make(chan os.Signal, 1) + signal.Notify(c, os.Interrupt) + time.Sleep(10 * time.Millisecond) + println("done") +} diff --git a/testdata/signalnotify.txt b/testdata/signalnotify.txt new file mode 100644 index 0000000000..19f86f493a --- /dev/null +++ b/testdata/signalnotify.txt @@ -0,0 +1 @@ +done