How to Shutdown a Golang Application in a Cleaner way

Leonardo Rodrigues Martins
4 min readJul 18, 2022

--

Photo by Michael Baccin on Unsplash

One of GO’s main features is the ability to be able to run multiple tasks in concurrency. It is a programming language widely used for its versatility, fast execution, and ease to use.

In many different applications, it is necessary to be able to gracefully shut it down, especially when it is dealing with some kind of state. Also, it allows to run tasks such as cleaning up used resources, to smoothly terminate independent processes and many others.

The term graceful shutdown is usually used in the context of Operational Systems. Which is the contrary of a forced shutdown, where the system goes off without the opportunity of performing the tasks it was supposed to before shutting down.

Fortunately, in Golang there are tools that allow us to identify shutdown requests and handle them properly.

Handling cancel signals with Contexts

While running multiple GO routines, we need to provide a coordinated way for them to exit smoothly. This can be achieved through the Context package. According to its documentation:

Package context defines the Context type, which carries deadlines, cancellation signals, and other request-scoped values across API boundaries and between processes.(context package docs)

Let’s consider the following example of a random sequence:

The for loop at the main function will receive values from the sequence and break when one of them is larger than 90. The problem is that even though we have exited from the loop, the go routine will continue trying to write to the channel in the background, while in reality we are running the next task.

That is a leak that we have in our program!

Well, in practice the go routine has no way of knowing that we no longer need it, and it will still be left hanging around. We need a way to communicate to the go routine that it can exit and that we no longer need it running. In a small program like this one, this is no big deal, but as our application grows and get more complex it can be an issue.

If you are interesting in learning more about the context package and its main features in GO, make sure to check out my post on How to use the Context Package in Go.

We can start creating a context and its respective cancel function.

ctx, cancel:= context.WithCancel(ctx)cancel() // The context get cancelled and ctx.Done() closes

Instead of trying to write to the sequence channel in the background, the go routine is going to exit. We’ll no longer have it hanging around while moving on to the other tasks in our program

Listening to the OS signals

Now comes the 2nd part of implementing a functional graceful shutdown, which is listening to the OS signals. Notably the signals responsible for interrupting or terminating the application.

The os/signal package allows us to listen and handle them before shutting down the program.

We can combine this functionality with the use of contexts by using the NotifyContext function:

NotifyContext returns a copy of the parent context that is marked done (its Done channel is closed) when one of the listed signals arrives, when the returned stop function is called, or when the parent context’s Done channel is closed, whichever happens first.(os/signal package docs)

An example of its usage:

ctx, stop := signal.NotifyContext(
context.Background(), os.Interrupt, syscall.SIGTERM)
  • The os.Interrupt signal is equivalent to interrupting the process pressing CTRL+C
  • SIGTERM is like sending a generic termination signal to the program.

Adding a Shutdown task

Let’s include a shutdown task on our random sequence generator. Whenever we exit from the program, we want to write to a file the last number received from the sequence:

Last number from random sequence: 8

We can achieve it combining the tools that we have seen in this article so far.

Our program is going to be listening to the random sequence indefinitely, until we interrupt or terminate the program.

...
Received: 54
Received: 97
Received: 103
Received: 76
Received: 20
Received: 63
Received: 33
Received: 97
^CClosing sequence
I'm leaving, bye!

At the same folder we can see the generated file last-num-from-seq.txt:

Last number from random sequence: 97

There we have it, the last number received from the sequence has been successfully saved.

Including a WaitGroup

I have noticed that in some cases, the main function can finish before the other GO routines have time do perform their cleanup tasks. To avoid this issue, we may introduce an WaitGroup to make sure that we are going to wait until all the cleanup jobs are finished properly:

Now we can be sure that file is going to be written when the program exits

Safely shutting down a server

A common way of using these tools is to gracefully shutdown a web server in GO. Otherwise, any open connection to it would be abruptly closed, which is not the best way from the user-experience point of view.

According to its documentation, after calling server.Shutdown(), the server instance will stop listening to new requests, then closing all idle connections, and then waiting indefinitely for connections to return to idle and then shut down.

Conclusion

In this article, we have seen an alternative way of shutting down a Golang application using the Context and os/signal packages, while seeing some examples of how to apply them. They can be used separately or combined to perform cleanup jobs or just avoid leaving leaky go routines behind. This is one of the important building blocks in the development of a solid and reliable application with Golang. I hope this article has been useful to you! Thanks for making it to the end!

--

--

Leonardo Rodrigues Martins

Software developer at Klever | Eager to share my experience and to learn more about technology.