Gracefully Shut Down Go Web Servers

Halil Yıldırım
Level Up Coding
Published in
3 min readDec 19, 2022

--

https://github.com/MariaLetta/free-gophers-pack

Gracefully shut down is a common task for web servers and it is a very important feature. We will investigate how to do it in Golang. Let’s dive in.

What is Gracefully shutdown?

If your web server is waiting your active requests to be handled before shutting down the server, it means you are gracefully shutting down the server.

Why is important?

Imagine you are visiting a store. They are closing at 10 pm. And you are inside at 9:55 pm and when it is 10 pm. they will close the store, won’t accept any new customer but help the customers inside and serve them then they will go home. They are gracefully shutting down the store.

You now find yourself in a store where the employees are not properly closing down operations for the day. They are open until 10 pm. At 9:55 pm, the employees abruptly stop helping customers and turn off the lights. They lock the doors and leave, even though there are still customers inside who have not completed their purchases. The employees go home without assisting the remaining customers or allowing them to check out. They have abruptly shut down the store without properly serving the customers inside.

It is the same idea for web servers. You get a signal to shut down the web server. Then you won’t accept any new requests, serve the active requests, and then close the web server.

Example

Let’s create a web server running on port 8000. Then create a goroutine to run the web server so it won’t block the code.

Running the web server

srv.ListenAndServe will run the web server on port 8000. If there is any error, we will log the error.

You may notice extra check errors.Is(err, http.ErrServerClosed). When you shut down or close the server, http.ErrServerClosed will be returned so it is not an error when running the web server so I will ignore the error.

Why use channel?

Recall receiving from a channel is a blocking operation. <-quit will block the program

I define a os.Signal channel to be able to block the application until receive a signal from operation system. signal.Notify will send a signal to quit channel when program is interrupted. You can do it with ctrl + c.

Why context timeout?

Imagine we have a bug in our code and we are shutting the server to use the new release that fixes this bug. Image this bug causes us to handle one of our active requests in minutes. So we shouldn’t wait that much. In the code below, I say, hey don’t wait more than 30 seconds. Shut down the server even if any active requests after 30 seconds. This is why use context with timeout. srv.Shutdown will gracefully shut down the server in 30 seconds.

package main

import (
"context"
"errors"
"log"
"net/http"
"os"
"os/signal"
"time"
)

func main() {
srv := &http.Server{
Addr: ":8000",
}
go func() {
if err := srv.ListenAndServe(); err != nil && !errors.Is(err, http.ErrServerClosed) {
log.Fatal(err)
}
}()
quit := make(chan os.Signal, 1)
signal.Notify(quit, os.Interrupt)
<-quit
ctx, cancel := context.WithTimeout(context.Background(), 30*time.Second)
defer cancel()
if err := srv.Shutdown(ctx); err != nil {
log.Fatal(err)
}
}

Do not forget to do it gracefully when you are shutting down your web server.

--

--