Signals
func GracefulStop(ctx context.Context, timeout time.Duration, stops ...func(context.Context) error) errorGracefulStop starts listening for signals when you call it, and stops before it returns.
What starts the shutdown
Section titled “What starts the shutdown”| 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.
Starting it yourself
Section titled “Starting it yourself”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.
A second signal
Section titled “A second signal”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.
Signals left alone
Section titled “Signals left alone”- 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
nohupignores SIGHUP, andGracefulStopdoesn’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 tosignal.Ignorebefore callingGracefulStop. - Other signals, such as SIGUSR1, are untouched.
Windows
Section titled “Windows”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.