年中アイス

いろいろつらつら

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)