🚥

Go v1.16に追加されたsignal.NotifyContextを試す

2021/02/22に公開

Go v1.16がリリースされましたね!
https://golang.org/doc/go1.16

追加された機能がいろいろありますが、今回はsignal.NotifyContextを試してみます!
https://future-architect.github.io/articles/20210207/

signal.NotifyContextで何ができるの?

シグナルをキャッチして、コンテキストを cancel させる処理を楽に書けるようになりました。
https://golang.org/pkg/os/signal/#NotifyContext

従来の書き方

これまでは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())
}

最後に

  • signal.NotifyContextを使うとより簡単にシグナルをハンドリングできるようになりました
  • シグナル処理をよくサボるので、ちゃんとやります

参考

https://github.com/nekoshita/golang-signal-notify-context-example
https://golang.org/pkg/os/signal/#NotifyContext
https://future-architect.github.io/articles/20210212/
https://mattn.kaoriya.net/software/lang/go/20200916090416.htm

Discussion