ℹ️ This article is based on Go 1.13.
signal provides signal handlers and allows our Go program to interact with the incoming signals. Let’s start with the listeners before diving into the internals.
The subscription to the signals is done via channels. Here is an example of a program that listens to any interruption signal or terminal resizing:
os.Signal channel listens to their own set of events. Here is a diagram with the subscription workflow of the previous example:
Go also gives the ability for a channel to stop being notified — function
Stop(os.Signal) — or to ignore signals — function
Ignore(...os.Signal). Here is a short example of each:
This program cannot be interrupted by
C and will never stop since the channel stopped listening to the signal for the terminal resizing before receiving from the channel a second time. Let’s now see how the
process phases that handle the incoming signals are built.
During the initialization phase, the
signal spawns a goroutine that runs in a loop and act as a consumer to process the signals. This loop sleeps until it gets notified. Here is the first step:
Then, when a signal reaches the program, the signal handler delegates it to a special goroutine called
gsignal. This goroutine is created with a bigger stack (32k, in order to fulfill the requirement by the different OS) that is fixed and cannot grow. Each thread (represented by
M) has an internal
gsignal goroutine to handle the signals. Here is the updated diagram:
gsignal analyzes the signal to check if it processable, and wakes up the sleeping goroutine along with sending the signal to the queue:
Synchronous signals, like
SIGFPE, are not manageable and will be converted to panics
Then, this looping goroutine can process it. It finds first the channels that have subscribed to this event, and pushes the signal to them:
The goroutine that is looping to process signals can be visualized in the traces via the tool
go tool trace:
A lock or block of
gsignal would make the signal handling under troubles. It also cannot allocate memory due to its fix size. This is why it is important to have two separated goroutines in the signal processing chain: one to queue the signals as soon as they arrived, and another one to process them looping on the same queue.
We can now update the illustration of the first section with the new components: