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)