It returns
GracefulStop returns the stops’ errors instead of calling os.Exit, so main picks the exit status
and its deferred calls run.
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 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,)// One after another, in this order,// all within 30 seconds of the signal.err := shutdown.GracefulStop(ctx, 30*time.Second, srv.Shutdown, queue.Drain, func(context.Context) error { return db.Close() },)// A signal starts the shutdown, and so// does the server failing.ctx, cancel := context.WithCancel(ctx)go func() { defer cancel() err := srv.ListenAndServe() if !errors.Is(err, http.ErrServerClosed) { log.Print(err) }}()
err := shutdown.GracefulStop(ctx, 10*time.Second, srv.Shutdown)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.
It waits for SIGINT, SIGTERM or SIGHUP, or for the context you passed to be done.
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.
A second signal ends the process at once, with exit status 1. The stops don’t finish and deferred calls don’t run.
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.
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)err := shutdown.GracefulStop( context.Background(), 10*time.Second, srv.Shutdown,)if err != nil { log.Fatalf("shutdown: %v", err)}Install the module, hand GracefulStop your server’s Shutdown, and press Ctrl+C. The guide walks through it.