Gracefully Shutdown Your Go Service

Iman Tung
3 min readJul 31, 2021
Source: lifesavvy.com

Make sure to tidy up your belonging and stop your activity when arrived at the destination before you leaving the train. Or else, you will lose your favorite toy (except if you all sleeping during the trip).

We should do the same thing when stopping the application. It should not die immediately but close the connection properly and finish the current process. The term is called graceful shutdown.

The application is stopped often

I don’t mean it stops unexpectedly because of fatal errors. Yet normal stop maybe more often than what your thinking now day.

With agile methodology, the feature delivers more often to the user which leads to more service deployment. Continuous Delivery make start and stop application became autonomous and practically can be anytime. Tech company put slogan of Zero Downtime, therefore stop the application should be smooth and seamless.

The graceful shutdown is also recommended by 12 FactorApp which is the best practice to creating the application as a service.

Implementation in Golang

defer in the main function is not what we actually looking for. The application keeps running to serve the incoming request and normally stops by the termination signal.

func main() {
app := &App{}
defer app.Stop() // not good because only trigger
// when the application stop naturally
// but not from `kill` or `Ctrl-C`


if err := app.Start(); err != nil {
log.Fatal(err)
}
}

We need to handle the signal from the OS to determine when to call the stop applications.

func main() {
app := &App{}
exitCh := make(chan os.Signal)
signal.Notify(exitCh,
syscall.SIGTERM, // terminate: stopped by `kill -9 PID`
syscall.SIGINT, // interrupt: stopped by Ctrl + C
)


go func() {
defer func() {
exitCh <- syscall.SIGTERM // send terminate signal when
// application stop naturally
}()
app.Start() // start the application
}()

<-exitCh // blocking until receive exit signal
app.Stop() // stop the application
}

What should be stopped

Usually, you don’t need to take care of the allocated resource like memory since OS will free it when the process terminates. We should focus on the communication and background process.

  • Close the persistent connection (database, WebSocket, etc)
  • Waiting for an ongoing process
  • Cancel the queued tasks

Database Connection

Calling db.Close() is best practice to make sure the running query is completed as mentioned in the documentation.

Close closes the database and prevents new queries from starting. Close then waits for all queries that have started processing on the server to finish.

func main() {
db, err := sql.Open("postgres", connStr) // Open connection
fatalIfError(err)
err = db.Ping() // Check if connection established
fatalIfError(err)
db.Close() // Close the connection
}

HTTP Server

Use server.Shutdown() instead of server.Close() for graceful shutdown of the HTTP Server.

func main() {
exitCh := make(chan os.Signal)
signal.Notify(exitCh, syscall.SIGTERM, syscall.SIGINT)
server := &http.Server{Addr: addr} go func() {
defer func() { exitCh <- syscall.SIGTERM }()
err := server.ListenAndServe()
if err != nil {
fmt.Println(err) // Don't use log.Fatal() or os.Exit()
// because it will interupt the shutdown
}

}()
<-exitCh
ctx := context.Background() // You may use context.WithTimeout()
// to set timeout


server.Shutdown(ctx) // Shutdown the server

}

Goroutines

Goroutines make the background process in Go fun. It is efficient and easy to use. But we still need to control and keep it on track. Fire-and-forgot goroutine execution should be avoided and only be used if we can tolerate failure.

func main() {
go func() {
// Don't execute goroutine without channel or waitgroup
}()
}

We can use sync.WaitGroup to wait for goroutine to finish

func main() {
var wg sync.WaitGroup

for i := 0; i < 10; i++ {
wg.Add(1) // Increment the WaitGroup counter
// before starting goroutine
// to avoid race
go func() {
defer wg.Done() // Decrement the counter

time.Sleep(5 * time.Second)
}()
}
wg.Wait() // Wait for goroutines to complete.
}

Epilogue

The article can’t talk about all the cases and may be missing some detail but hopefully, it can bring awareness to the importance of the Graceful Shutdown. Let put comments if you have an interesting experience or few other tricks related to the topic.

--

--

Iman Tung

Technology to write, life to grateful. Overthinking is good, only if it has the output. Fundamental is the main concern.