https://zenn.dev/nekoshita/articles/dba0a7139854bb

Go v1.16がリリースされましたね!

追加された機能がいろいろありますが、今回はsignal.NotifyContextを試してみます!

signal.NotifyContextで何ができるの?

シグナルをキャッチして、コンテキストを cancel させる処理を楽に書けるようになりました。

従来の書き方

これまではsignal.Notifyでシグナルをキャッチして、context.WithCancelで作成したコンテキストをcancelする処理を書く必要がありました。

package main

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

func main() {
	ctx, cancel := context.WithCancel(context.Background())
	c := make(chan os.Signal, 1)
	signal.Notify(c, os.Interrupt, os.Kill)
	go func() {
		select {
		case <-c:
			fmt.Fprintln(os.Stderr, "signal received")
			cancel()
		case <-ctx.Done():
		}
	}()

	doSomethingAwesome(ctx)
}

signal.NotifyContextを使った書き方

signal.NotifyContextを使うと、こんなにシンプルにかけるようになりました。

package main

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

func main() {
	ctx, stop := signal.NotifyContext(context.Background(), os.Interrupt)
	defer stop()

	doSomethingAwesome(ctx)
}

signal.NotifyContextを使う例

goroutineを複数起動し、signalで一括終了する

package main

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

func blocking(ctx context.Context, wg *sync.WaitGroup) {
	defer wg.Done()
	fmt.Println("worker started")
	<-ctx.Done()
	fmt.Println("worker canceled")
}

func main() {
	ctx, stop := signal.NotifyContext(context.Background(), os.Interrupt)
	defer stop()

	var wg sync.WaitGroup
	wg.Add(3)
	go blocking(ctx, &wg)
	go blocking(ctx, &wg)
	go blocking(ctx, &wg)

	wg.Wait()
}

ウェブサーバーをgracefulにシャッドダウンする

package main

import (
	"context"
	"fmt"
	"io"
	"log"
	"net/http"
	"os"
	"os/signal"
	"time"
)

func main() {
	ctx, stop := signal.NotifyContext(context.Background(), os.Interrupt)
	defer stop()

	http.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) {
		io.WriteString(w, "hello!")
	})

	server := &http.Server{
		Addr:    ":8080",
		Handler: nil,
	}
	go func() {
		<-ctx.Done()
		ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second)
		defer cancel()
		server.Shutdown(ctx)
	}()
	fmt.Println("starting server at :8080")
	log.Fatal(server.ListenAndServe())
}

最後に