Skip to content

runtime: deliver signals under the threads scheduler when blocked on I/O#5530

Open
0pcom wants to merge 1 commit into
tinygo-org:devfrom
0magnet:fix-threads-signal-delivery
Open

runtime: deliver signals under the threads scheduler when blocked on I/O#5530
0pcom wants to merge 1 commit into
tinygo-org:devfrom
0magnet:fix-threads-signal-delivery

Conversation

@0pcom

@0pcom 0pcom commented Jul 15, 2026

Copy link
Copy Markdown

Problem

Under the threads scheduler (the default on Linux/macOS), a program blocked purely on I/O, channels or mutexes never observes an OS signal. For example, a server that does signal.Notify(c, os.Interrupt); <-c while its goroutines are blocked on network I/O ignores Ctrl+C indefinitely and has to be killed.

The cause: checkSignals() — which resumes the parked os/signal signal_recv goroutine — is only ever reached from sleepTicks() (in runtime_unix.go). So a signal is only noticed while some goroutine happens to be inside time.Sleep. time.NewTicker/time.After go through the timer queue (timerRunner), not sleepTicks, so a program that blocks on I/O/channels can ignore SIGINT indefinitely.

The cooperative and multicore schedulers don't have this problem because they call checkSignals() from their idle loop (waitForEvents). The threads scheduler has no such loop, so nothing consumes signalFutex and resumes signal_recv.

Fix

Start a dedicated signal-watcher thread the first time a signal is enabled, gated to the threads scheduler (!hasScheduler && hasParallelism, which is true only there). It blocks on signalFutex and calls checkSignals() on wake — mirroring the signal half of waitForEvents(). It is a compile-time no-op for every other scheduler: the cooperative/cores schedulers already handle signals from their idle loop, and the none scheduler has no goroutines.

Testing

A channel/Accept-blocked program with no time.Sleep anywhere ignores SIGINT before this change and exits cleanly after it. Also verified against a real network daemon that blocks on I/O: SIGINT now triggers graceful shutdown, both idle and under load.

…e sleeps

Under the threads scheduler there is no cooperative idle loop, so checkSignals()
— which resumes the parked os/signal signal_recv goroutine — was only ever
reached from sleepTicks(). That means a signal (e.g. SIGINT/Ctrl+C) was only
noticed while some goroutine happened to be inside time.Sleep. A program blocked
purely on I/O, channels, mutexes or timers (time.NewTicker uses the timer queue,
not sleepTicks) would never observe the signal at all.

Start a dedicated signal-watcher thread the first time a signal is enabled,
gated to the threads scheduler (!hasScheduler && hasParallelism). It blocks on
signalFutex and calls checkSignals() on wake, mirroring the signal half of
waitForEvents() that the cooperative scheduler runs from its idle loop. Other
schedulers are unaffected (the start is a compile-time no-op for them).

Verified: a channel/Accept-blocked program with no time.Sleep now receives
SIGINT, and the skycoin daemon (previously unkillable with Ctrl+C under TinyGo)
now shuts down cleanly on SIGINT, both idle and during active block sync.

(cherry picked from commit ada7ab6)
@dgryski
dgryski requested a review from aykevl July 15, 2026 20:50
// signalFutex and resumes the signal-receiving goroutine (signal_recv) whenever
// a signal arrives, decoupling signal delivery from sleepTicks(). It mirrors the
// signal half of waitForEvents(), which the threads scheduler never calls.
func signalWatcher() {

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is called as a Go routine with no exit condition. I know that waitForEvents() already has the same issue, but it would be pretty nice to have a way for a cleaner exit.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants