golangでのシグナルハンドリング
golangでデーモンやWebサーバなどの設定再読み込みなどをどうしようかなと考えてた時に、fluentdがシグナルでいろいろやってたな*1と思ったので、golangだとどうやるか調べてみました。
全体はこれ
動作の確認
ビルドして実行ファイル作成
go build
実行します。(以降このターミナルをAとします)
./signal
他のターミナルからプロセスを確認します。(以降このターミナルをBとします)
ps | grep signal 9999 ttys003 0:00:00 ./signal XXXX ttys003 0:00.00 grep --color signal
BからSIGHUPシグナルを送ります
kill -SIGHUP XXXX
Aに、hungupが表示されます(実際はハングアップしてないです)
hungup
今度は、AでCtrl+cを押すと、Warikomiと表示されます
Warikomi
SIGHUPとSIGINTは、./signalが動き続けます。
Bから、SIGQUITを送ります
kill -SIGQUIT XXXX
Aが終了してターミナル入力に戻ります。
stop and core dump
もうAから一度起動して、Bからpsで確認後、kill -SIGTERM XXXXとすることで、同様に終了します
force stop
コード解説
最初にos.Signalのchannelを作り、signal.Notifyを使って、どのシグナルが発生したときにchannelに流すかを登録します。
signal_chan := make(chan os.Signal, 1) signal.Notify(signal_chan, syscall.SIGHUP, syscall.SIGINT, syscall.SIGTERM, syscall.SIGQUIT)
そのchannelをgoroutineに渡して、シグナルが発生した時の分岐を書いています。今回は、終了用のchannelを使って、終了をブロックしています。exit_chan <- 0しているところに、os.Exit(0)と書いてしまうと、goroutineそのままにmain()が終了してしまいます。本当はデーモン等でずっと処理をしている裏で、別のgoroutineがシグナルを待ち受けているという感じになると思います。その際はchannelでやり取りをするほうがすっきりしそうです。
exit_chan := make(chan int) go func() { for { s := <-signal_chan switch s { // kill -SIGHUP XXXX case syscall.SIGHUP: fmt.Println("hungup") // kill -SIGINT XXXX or Ctrl+c case syscall.SIGINT: fmt.Println("Warikomi") // kill -SIGTERM XXXX case syscall.SIGTERM: fmt.Println("force stop") exit_chan <- 0 // kill -SIGQUIT XXXX case syscall.SIGQUIT: fmt.Println("stop and core dump") exit_chan <- 0 default: fmt.Println("Unknown signal.") exit_chan <- 1 } } }() code := <-exit_chan os.Exit(code)