OS Signals and the Runtime
OS Signals and the Runtime
Overview
Unix signals interrupt processes asynchronously. Go multiplexes them through the runtime so user code can use os/signal channels instead of raw C handlers—while still coordinating with the scheduler and cgo.
Diagram: signal path (teaching)
kernel signal
│
v
runtime notes signal
│
v
os/signal demux → Notify channel
│
v
user G handles (e.g. Shutdown)
os/signal API
ctx, stop := signal.NotifyContext(context.Background(), os.Interrupt, syscall.SIGTERM)
defer stop()
<-ctx.Done()
// graceful shutdownch := make(chan os.Signal, 1)
signal.Notify(ch, syscall.SIGTERM)Buffer the channel—signals can be missed if send would block.
Interactions
| Area | Note |
|---|---|
| Multiple Notify | Same signal can fan out |
| Reset/Stop | Undo registration |
| Windows | Different signal story; Interrupt exists |
| cgo | Foreign code may install handlers—careful |
| Panic dumps | GOTRACEBACK, SIGQUIT often stack dump |
Graceful shutdown pattern
sequence (top → bottom):
actors: SIGTERM, main, http.Server
SIGTERM --> main : NotifyContext cancel
main --> http.Server : Shutdown(ctx)
http.Server --> main : drains then return
Experiment
# terminal 1
go run .
# terminal 2
kill -TERM $(pidof yourbin)package main
import (
"context"
"fmt"
"os"
"os/signal"
"time"
)
func main() {
ctx, stop := signal.NotifyContext(context.Background(), os.Interrupt)
defer stop()
fmt.Println("running; press Ctrl+C")
select {
case <-ctx.Done():
fmt.Println("got", ctx.Err())
case <-time.After(30 * time.Second):
fmt.Println("timeout")
}
}What to notice: Cancel is process-wide coordination; pair with server Shutdown, not os.Exit mid-request.
Try next: Trap SIGTERM in a binary under Docker docker stop and confirm clean logs.