Graceful shutdown of Golang servers using Context and OS signals

Pinku Deb Nath
3 min readMay 26, 2019

--

Whenever a server needs to shut down for various reasons, the common one being OS interrupts, we would want our servers to shut down gracefully. We would want our golang server to stop receiving new requests while complete the ongoing requests and return their responses and then finally shut down.

We can achieve this task by creating a context object with a cancel callback function and the shutdown method provided by the http.Server interface. We keep listening for OS interrupts via a channel and then call the cancel function for the context. We use the context to create a server instance and then shut down the server whenever the context is done.

First of all, there is a working example of the graceful server. We will go through the sections of the code below.

Here is the output:

$ go run main.go
2019/05/26 11:05:30 server started
^C2019/05/26 11:05:31 system call:interrupt
2019/05/26 11:05:31 server stopped
2019/05/26 11:05:31 server exited properly

In the main function, first we are creating a channel for listening to OS signals and connecting OS interrupts to the channel.

c := make(chan os.Signal, 1)signal.Notify(c, os.Interrupt)

Then we create a context with cancel() callback function and run a go routine in which the cancel() function is called once an OS interrupt is received. Hence, the cancelling of context happens after an OS interrupt.

ctx, cancel := context.WithCancel(context.Background())go func() {    oscall := <-c    log.Printf("system call:%+v", oscall)    cancel()}()

Finally, we pass the reference of the context to the serve function to create and run a server.

if err := serve(ctx); err != nil {    log.Printf("failed to serve:+%v\n", err)}

In the serve function(), first we create a generic mux with a handler for the single route / and casting the func(w http.ResponseWriter, r *http.Request with http.HandlerFunc which has the ServeHTTP function needed to satisfy the http.Handler interface for the last argument of the mux.Handler. Sorry for the mouthful :P

mux := http.NewServeMux()mux.Handle("/", http.HandlerFunc(    func(w http.ResponseWriter, r *http.Request) {        fmt.Fprintf(w, "okay")    },))srv := &http.Server{    Addr:    ":6969",    Handler: mux,}

Now, we will run the server in a seperate go routine, instead of running in the main routine. After calling the go routine, we wait for the passed context ctx to be canceled by listening on the channel returned by ctx.Done().

go func() {    if err = srv.ListenAndServe(); err != nil && err != http.ErrServerClosed {log.Fatalf("listen:%+s\n", err)    }}()<-ctx.Done()

The http.Server comes with a Shutdown() method which can be used to close the server gracefully, so that it stops taking new requests and finishes the currently handled requests. The Shutdown() takes as argument a context with timeout. The timeout is the maximum allowed for the current requests to complete.

ctxShutDown, cancel := context.WithTimeout(context.Background(), 5*time.Second)defer func() {    cancel()}()if err = srv.Shutdown(ctxShutDown); err != nil {    log.Fatalf("server Shutdown Failed:%+s", err)}if err == http.ErrServerClosed {    err = nil}

Pretty simple!

Thank you for reading my post. Looking forward to any advice, suggestions, tips, and tricks regarding Golang and software engineering in general.

image source

--

--