Handling OS Signals To Guarantee Graceful Degradation In Go

Edward Pie
4 min readJul 9, 2018

In programming, it’s usually not advised to use a resource unless you know how it will be released at the end of your program. Failing to heed to this simple advise can create a huge mess that’s not only difficult to track, but also very difficult to solve. For example, imagine being tasked to identify and solve a problem of leaky goroutines.

Deferred Functions Are Not Always Guaranteed To Execute

Go gives us the “defer” keyword which unsurprisingly is used to defer the execution of functions at the point when your main function is just about to return. However, your deferred functions are not always guaranteed to execute before the main function returns. For example, invoking os.Exit or pressing CTRL + C to interrupt your program will result in your deferred functions not getting called. Let’s see a demonstration in the code below :

invoking os.Exit prevents deferred functions from executing

Looking at the output from the code above, the deferred function never got the chance to execute because I invoked os.Exit. The image below shows the output of the same code when I hit CTRL + C, instructing the Operating System to interrupt the program. It is evident that in both cases, the deferred function never got the chance to execute before the process got killed.

pressing ctrl + c to interrupt process

However, deferred functions are guaranteed to execute in case of panic. For this reason, I don’t buy the idea of never panicing in your go programs. Often, there is the need to panic at least once (or few more times) in main and if you’re creating a package to be used by others, just bubble errors up until they can be handled.

Let’s modify the code slightly to deliberately panic in main just to see what happens.

panicing guarantees the execution of deferred functions

Seen? Though we panic’ed, the deferred function was guaranteed a window to execute before termination main. Another advantage of panicing when needed is the fact that you get to see the stacktrace of your program. With os.Exit or Ctrl + C, you miss out on seeing the stacktrace.

Releasing Resource In Server Environments

If you’ve ever done server-side programming in go, statements like the following aren’t hard to find in codebases :

idiomatic way to defer release of resources in Go

Under ideal conditions, the code above will work absolutely fine. But, the environments within which our programs run are more hostile than the idle environments in our development boxes. Many things could go wrong in production and this code won’t be safe enough. For example, the OS for many reasons can decide to send SIGINT (same as CTRL + C) or SIGTERM to our server process and get it terminated. And, knowing what you know now, I’m sure you’re aware that all the deferred statements (which are usually used for resource deallocations) won’t get called. In this case, you’re at the mercy of the OS to clean up these resources otherwise, there’s chaos and unpredictability of your server.

Handle OS Signals To Make Your Programs Resilient To Surprises

Thankfully, the OS signals our programs before it takes actions against them and Go as usual gives us a beautiful way of intercepting these signals so that we can gracefully handle them. Let’s see how in the snippet below :

intercepting OS signals in Go

On line 11, an unbuffered channel within which signals (specifically, values of os.Signal) will be passed is created.

Line 12 indicates that our program is interested in being notified whenever the SIGINT or SIGTERM signals are received by the OS to act on our program. SIGINT (os.Interrupt) is available in the os package because it is the signal that has been implemented by all operating systems. Others like SIGTERM are OS-specific hence belonging to a different package.

When any of these signals targeting our main process is received by the OS, the signal will be sent to our program using the channel created on line 11.

The “select” block between lines 16–20 waits patiently until any of the signals we’re interested in gets sent over the channel. Once received, you can perform all the necessary house-keeping chores and gracefully deallocate resources before terminating.

And, if you’re interested in handling the signals differently, just received the signals into a variable and handle them appropriately based on the type of signal received.

As powerful as this sounds, it is simple and beautiful just because it’s Go!

If you liked this post, don’t forget to follow me on medium for more and also on Youtube. I’ve recorded tonnes of Golang videos that I’ll be uploading soon. Don’t miss a notification.

--

--

Edward Pie

I write code for anything that has the capacity to run code. I love Machine Learning & Computer Vision, am addicted to Python, I adore Golang, Swift & Java.