In the Go 1.16 update, the signal package adds a function NotifyContext, which allows us to write Graceful Graceful Restart services.

There are two main aspects to gracefully restarting a service:

  • Exit the old service requiredGraceful Shutdown, does not force to kill the process, does not leak system resources.
  • Take turns to restart service instances in a cluster to ensure service continuity.

The second question has to do with deployment methods. I’ll write a discussion for another day. Today we’ll focus on how to exit gracefully.

First in the code, when you use an external resource, be sure to defer by calling the Close() method. Then we need to intercept the interrupt signal from the system and ensure that the program exits in an orderly manner after receiving the interrupt signal, so that all defer will be executed.

In the old days, it would read something like this:

func everLoop(ctx context.Context) {
LOOP:
    for {
        select {
        case <-ctx.Done():
            // Exit the infinite loop after receiving a signal
            break LOOP
        default:
            // Simulate business logic with a sleep
            time.Sleep(time.Second * 10)}}}func main(a) {
    // Create a Context that can be manually cancelled
    ctx, cancel := context.WithCancel(context.Background())

    SIGINT (Ctrl+ C), SIGTERM
    // In systemd and Docker, SIGTERM is issued first, and SIGKILL is issued after a period of time
    // So SIGKILL is not captured here
    sig := make(chan os.Signal, 1)
    signal.Notify(sig, syscall.SIGINT, syscall.SIGTERM)
    go func(a) {
        <-sig
        cancel()
    }()

    // Start an infinite loop and exit on signal
    everLoop(ctx)
    fmt.Println("graceful shuwdown")}Copy the code

Now with the new function, this section is easier:

func main(a) {
    // Monitoring system signals and creating Context are now done in one step
    ctx, stop := signal.NotifyContext(context.Background(), syscall.SIGINT, syscall.SIGTERM)
    // CTX is automatically stopped when a signal is received.
    defer stop()

    // Start an infinite loop and exit on signal
    everLoop(ctx)
    fmt.Println("graceful shuwdown")}Copy the code

Finally, it is worth noting that if more than one Goroutine uses the context to cancel, we need to make sure that all of them exit before we can exit the main process.

There is no elegant way to do this, but pass a pointer to sync.waitGroup for each goroutine and Wait before exiting.

Thanks to Golang, what used to require a lot of code in other languages can now be easily implemented in a few lines. Make it a standard part of your service.

Finally, I discovered this new way of writing while writing the latest project Server. Server fan allows you to take wechat public number as a portable Terminal to control your Server. An example of the above usage can be found in its Agent main function.