You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
runtime: deliver os/signal notifications under the threads scheduler
signal.Notify delivers a signal on hosted linux and macOS only while some other
goroutine is in time.Sleep. Both hosts default to -scheduler=threads. A program
that installs a handler and then waits on the channel, which is what a command
does to stop on SIGINT, waits for ever.
The receiving goroutine in os/signal calls signal_recv, which parks itself with
task.Pause when nothing is pending. Only checkSignals resumes it, and on the
receive path the callers of checkSignals are waitForEvents, the idle hook of
the cooperative scheduler, and sleepTicks. With threads there is no scheduler
loop, so waitForEvents never runs and only a sleep elsewhere in the program
lets a signal through.
Give the receiver a wait that works on a thread. signal_recv now blocks on a
futex of its own that the handler wakes, with the same 0/1 protocol that the
handler already uses on signalFutex. It cannot share signalFutex, because
sleepTicks waits on that one too and swaps it back to zero, so a time.Sleep
anywhere in the program would take the wakeup of the receiver. The handler only
gets an atomic store and a futex wake syscall, which are both safe in a signal
handler on an arbitrary thread. The stop-the-world signal of the GC uses a
different handler that this does not touch.
signalWaitUntilIdle, which signal.Stop and signal.Reset call before they
return, had the same problem from the other side. It spun on Gosched, which is
a no-op with threads, so it used a core until the receiver emptied the last
signal. It now waits on a futex that signal_recv wakes.
The cooperative path keeps its behaviour. The two versions live in
signal_cooperative.go and signal_threads.go, split on scheduler.threads like
the schedulers themselves.
testdata/signal.go grows a first phase that waits on the channel and nothing
else. Built from the current dev branch on macOS 26.6 arm64 it prints nothing
and hangs. With this change it prints the expected output and exits.
0 commit comments