Handling UNIX Signals

Let’s learn about handling UNIX signals.

UNIX signals offer a very handy way of interacting asynchronously with our applications. However, UNIX signal handling requires the use of Go channels that are used exclusively for this task. So, it would be good to talk a little about the concurrency model of Go, which requires the use of goroutines and channels for signal handling.

Goroutine and channel

A goroutine is the smallest executable Go entity. In order to create a new goroutine, we have to use the go keyword followed by a predefined function or an anonymous function—the methods are equivalent. A channel in Go is a mechanism that, among other things, allows goroutines to communicate and exchange data. If you are an amateur programmer or are hearing about goroutines and channels for the first time, do not panic. Goroutines and channels are explained in much more detail in later chapters.

Note: In order for a goroutine or a function to terminate the entire Go application, it should call os.Exit() instead of return. However, most of the time, we should exit a goroutine or a function using return because we just want to exit that specific goroutine or function and not stop the entire application.

The presented program handles SIGINT, which is called syscall.SIGINT in Go and SIGINFO separately and uses a default case in a switch block for handling the remaining cases (other signals). The implementation of that switch block allows us to differentiate between the various signals according to our needs.

There exists a dedicated channel that receives all signals, as defined by the signal.Notify() function. Go channels can have a capacity—the capacity of this particular channel is 1 in order to be able to receive and keep one signal at a time. This makes perfect sense because a signal can terminate a program and there is no need to try to handle another signal at the same time. There is usually an anonymous function that is executed as a goroutine and performs the signal handling and nothing else. The main task of that goroutine is to listen to the channel for data. Once a signal is received, it is sent to that channel, read by the goroutine, and stored into a variable— at this point, the channel can receive more signals. That variable is processed by a switch statement.

Note: Some signals cannot be caught, and the operating system cannot ignore them. So, the SIGKILL and SIGSTOP signals cannot be blocked, caught, or ignored, and the reason for this is that they allow privileged users, as well as the UNIX kernel, to terminate any process they desire.

Coding example

Let’s look at the signals.go code:

Get hands-on with 1400+ tech skills courses.