In this article, we will introduce to you why signal.Notify should use buffered channel. When we want to do graceful shutdown, we will use this function to shut down the service or connection normally. Through signal, we can detect the source of the signal and do the follow-up work (shut down DB connection, check if the job is finished … etc.).

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
package main

import (
    "fmt"
    "os"
    "os/signal"
)

func main() {
    // Set up channel on which to send signal notifications.
    // We must use a buffered channel or risk missing the signal
    // if we're not ready to receive when the signal is sent.
    c := make(chan os.Signal, 1)
    signal.Notify(c, os.Interrupt)

    // Block until a signal is received.
    s := <-c
    fmt.Println("Got signal:", s)
}

The above example clearly shows that if you don’t use a buffered channel, you run a certain risk of not catching the Signal. So why is there this description? Let’s take a look with other examples.

Use unbuffered channel

Change the code to the following:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
package main

import (
    "fmt"
    "os"
    "os/signal"
)

func main() {
    c := make(chan os.Signal)
    signal.Notify(c, os.Interrupt)

    // Block until a signal is received.
    s := <-c
    fmt.Println("Got signal:", s)
}

Run the above code and press ctrl + c, you will see Got signal: interrupt, then we have some very complicated work to do before accepting channle, use time.Sleep to test it first.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
package main

import (
    "fmt"
    "os"
    "os/signal"
)

func main() {
    c := make(chan os.Signal)
    signal.Notify(c, os.Interrupt)

    time.Sleep(5 * time.Second)

    // Block until a signal is received.
    s := <-c
    fmt.Println("Got signal:", s)
}

You will find that during the five seconds, no matter how you press ctrl + c, the program will not stop, and after five seconds, the program will not stop. You need to press ctrl + c again, and then the program will stop. What we expect is that if you press ctrl + c any time during the first five seconds, theoretically you will receive the first signal normally after five seconds. Let’s see why.

Reasons for formation

We open Golang’s singal.go file, find the process func, and you can see part of the code:

1
2
3
4
5
6
7
8
9
for c, h := range handlers.m {
    if h.want(n) {
        // send but do not block for it
        select {
        case c <- sig:
        default:
        }
    }
}

As you can see in the above code, if you use the unbuffered channel, any signal received within 5 seconds will run into the default condition, so the channel will not receive any value, which is why any action within 5 seconds will not be received at all after 5 seconds. To avoid this, we usually set the signal channel to buffer 1 to avoid interrupting the program to make sure the main program can receive a signal.