Skip to content

Signals

func GracefulStop(ctx context.Context, timeout time.Duration, stops ...func(context.Context) error) error

GracefulStop starts listening for signals when you call it, and stops before it returns.

Signal Usually sent by
SIGINT Ctrl+C in a terminal
SIGTERM kill, systemd, container runtimes and orchestrators asking a process to stop
SIGHUP the terminal going away, such as a closed SSH session

Go’s default for each of these is to end the program at once. GracefulStop turns that into a shutdown: your stops run first, and your main decides how to exit.

The shutdown also starts when the ctx you pass is done. Pass context.Background() when only a signal should start it, and a context you cancel when something else should, such as the server failing:

ctx, cancel := context.WithCancel(context.Background())
go func() {
defer cancel() // the server stopped: shut down too
if err := srv.ListenAndServe(); !errors.Is(err, http.ErrServerClosed) {
log.Print(err)
}
}()
err := shutdown.GracefulStop(ctx, 10*time.Second, srv.Shutdown)

The stops don’t inherit ctx’s cancellation, only its values, so they still get their full deadline.

The second signal GracefulStop receives ends the process at once, with exit status 1. It doesn’t matter which signal it is: pressing Ctrl+C twice, or a SIGTERM after a SIGINT, both count.

GracefulStop counts the signals it receives, not shutdowns. When the shutdown started because ctx was done, it takes two signals to end the process. That matters when ctx comes from signal.NotifyContext: one Ctrl+C cancels that context and reaches GracefulStop too, and it must not end the process on its own.

  • SIGQUIT keeps Go’s default: Ctrl+\ prints the stack of every goroutine and exits with status 2. When a shutdown hangs, that’s how to see what it waits for.
  • Ignored signals stay ignored. A program started under nohup ignores SIGHUP, and GracefulStop doesn’t listen for it, so the program keeps running when its terminal goes away. The same goes for SIGINT in a job a script runs in the background, and for any of the three that your program passed to signal.Ignore before calling GracefulStop.
  • Other signals, such as SIGUSR1, are untouched.

On Windows, Go turns Ctrl+C and Ctrl+Break into SIGINT, and closing the console window, logging off or shutting down into SIGTERM; SIGHUP never arrives. Windows ends the process shortly after those last three whatever it does, so keep the timeout short there.