signal.Notify したチャンネルを close すると死ぬ(可能性がある)
TL; DR
signal.Stop
しよう
https://golang.org/pkg/os/signal/#Stop
検証
ダメな例
package main import ( "fmt" "os" "os/signal" "syscall" "time" ) func main() { c := make(chan os.Signal, 1) signal.Notify(c, syscall.SIGINT, syscall.SIGTERM) close(c) fmt.Println("C-c plz") time.Sleep(10 * time.Second) }
$ go run main.go C-c plz ^Cpanic: send on closed channel goroutine 5 [running]: os/signal.process(0x10dbc60, 0xc00002aa30) /usr/local/Cellar/go/1.11.1/libexec/src/os/signal/signal.go:227 +0x163 os/signal.loop() /usr/local/Cellar/go/1.11.1/libexec/src/os/signal/signal_unix.go:23 +0x52 created by os/signal.init.0 /usr/local/Cellar/go/1.11.1/libexec/src/os/signal/signal_unix.go:29 +0x41 exit status 2
ある channel がクローズ済かは送信者はわからないので仕方がない。
大丈夫な例
package main import ( "fmt" "os" "os/signal" "syscall" "time" ) func main() { c := make(chan os.Signal, 1) signal.Notify(c, syscall.SIGINT, syscall.SIGTERM) signal.Stop(c) // signal.Stop を追加した close(c) fmt.Println("C-c plz") time.Sleep(10 * time.Second) }
$ go run main.go C-c plz ^Csignal: interrupt
OK
背景
graceful restart 的なことをするときに、シグナルでハンドリングをしていた。のだが、場合によっては即座に死んでほしくないパターンがあった。API通信相手がメンテ中だとわかっている場合とかは即死すると即死→メンテ→即死→メンテの無限ループって怖くね?状態になる。
しょうがないのでメンテのときはある程度待ってから死ぬようにしてみるか、と思って書いているわけだが、シグナルハンドリングが終わったあとの息が長くなるとリソースの解放漏れが気になってきた。そこでさくっと close しようとしたら、これ死ぬんじゃね?って思って調べて今に至る。
ここまで書いた後でさっさと死んで起動直後にメンテ中か確認すれば特に悩む必要なかったなって思った。おしまい。