Go by Example

Signals

signal.NotifyContext ties a context to SIGINT/SIGTERM - cancel it to propagate graceful shutdown to every goroutine that respects context cancellation.

signal.Notify registers a channel to receive OS signals. signal.NotifyContext (Go 1.16) is the idiomatic way to tie a context.Context to signal receipt - when the signal arrives, the context is cancelled and the cancellation propagates through the entire call stack.

signal.Notify delivers matching signals to the channel. Always create a buffered channel (capacity 1) so the signal is not dropped if the goroutine is not ready to receive.

package main
 
import (
    "fmt"
    "os"
    "os/signal"
    "syscall"
)
 
func main() {
    sigs := make(chan os.Signal, 1)
    signal.Notify(sigs, syscall.SIGINT, syscall.SIGTERM)
 
    fmt.Println("running, press Ctrl+C to stop")
    sig := <-sigs
    fmt.Println("\nreceived signal:", sig)
}

signal.NotifyContext returns a context that is cancelled when the signal arrives. Pass this context to your server or worker - they stop cleanly when cancelled.

package main
 
import (
    "context"
    "fmt"
    "os/signal"
    "syscall"
    "time"
)
 
func runWorker(ctx context.Context) {
    for {
        select {
        case <-ctx.Done():
            fmt.Println("worker shutting down:", ctx.Err())
            return
        case <-time.After(500 * time.Millisecond):
            fmt.Println("worker tick")
        }
    }
}
 
func main() {
    ctx, stop := signal.NotifyContext(context.Background(), syscall.SIGINT, syscall.SIGTERM)
    defer stop()
 
    go runWorker(ctx)
 
    <-ctx.Done()
    fmt.Println("signal received, waiting for worker")
    time.Sleep(200 * time.Millisecond)
    fmt.Println("shutdown complete")
}

In production

Handle SIGTERM for graceful shutdown in containerised services -- Kubernetes sends SIGTERM before SIGKILL and waits terminationGracePeriodSeconds (default 30 s) for the process to exit cleanly. If your server ignores SIGTERM and Kubernetes sends SIGKILL, in-flight requests are abruptly dropped. Use signal.NotifyContext to tie a context to SIGTERM receipt, then pass that context to http.Server.Shutdown so it waits for in-flight requests to complete before closing. Always call stop() (the function returned by signal.NotifyContext) when you are done -- it releases the signal notification resources and re-enables the default signal behaviour so a second Ctrl+C terminates the process immediately rather than hanging. For multi-signal flows (first signal = drain, second signal = force exit), use signal.Notify with a buffered channel and handle the two signals separately in a select. Never call signal.Notify with an unbuffered channel in production -- if the goroutine is not ready to receive when the signal arrives, the delivery is skipped entirely.

Enjoyed this? Get more essays on software craft delivered to your inbox.

Subscribe free