Skip to content
v2See what changed

Stop on a signal, gracefully

On SIGINT, SIGTERM or SIGHUP, run your stop functions in order, with one deadline, and get their errors back. A second signal ends the process at once.

go get github.com/vardius/shutdown/v2
main.go
go func() {
err := srv.ListenAndServe()
if !errors.Is(err, http.ErrServerClosed) {
log.Fatal(err)
}
}()
// Blocks until SIGINT, SIGTERM or SIGHUP,
// then gives requests in flight 10 seconds.
err := shutdown.GracefulStop(
context.Background(),
10*time.Second,
srv.Shutdown,
)
  • 3signals start a shutdown: SIGINT, SIGTERM and SIGHUP
  • 2ndsignal ends the process at once, for when a stop hangs
  • 0dependencies beyond the standard library
  • 100%test coverage, with real signals and a fake clock

Features

It returns

GracefulStop returns the stops’ errors instead of calling os.Exit, so main picks the exit status and its deferred calls run.

One deadline

The stops share a context that is done a set time after the shutdown starts. Pass srv.Shutdown as it is.

In order, every error kept

Stops run one after another, in the order you pass them. Every one is called, and their errors come back joined.

A way out

A second signal ends the process at once, with exit status 1, for when a stop hangs.

Go's defaults kept

SIGQUIT still dumps every goroutine, and a signal the program ignores, such as SIGHUP under nohup, stays ignored.

Small and tested

One function, no dependencies, and tests that send real signals and run on a fake clock.

How it works

GracefulStop blocks your main until it’s time to stop, then runs your stops on the same goroutine and returns.

  1. It waits for SIGINT, SIGTERM or SIGHUP, or for the context you passed to be done.

  2. It runs your stops, one after another, with a context that is done timeout later. Each gets the same deadline; each should return once it passes.

  3. A second signal ends the process at once, with exit status 1. The stops don’t finish and deferred calls don’t run.

  4. It returns the stops’ errors joined, or nil, and stops listening for signals. main decides what to do with them.

Upgrading

v1 ran one function and called os.Exit(0). v2 hands your stops a deadline, returns their errors, and leaves the exit to you.

v1
shutdown.GracefulStop(func() {
ctx, cancel := context.WithTimeout(
context.Background(), 10*time.Second)
defer cancel()
if err := srv.Shutdown(ctx); err != nil {
log.Printf("shutdown: %v", err)
}
})
// never reached: v1 calls os.Exit(0)
v2
err := shutdown.GracefulStop(
context.Background(),
10*time.Second,
srv.Shutdown,
)
if err != nil {
log.Fatalf("shutdown: %v", err)
}

Drain your first server in five minutes

Install the module, hand GracefulStop your server’s Shutdown, and press Ctrl+C. The guide walks through it.