Go言語を使ったOSシグナルの処理
概要
os
、os/signal
とsyscall
パッケージ、あとはチャンネルを使えばOSシグナルを処理できるよというお話
コードサンプル
コードとしては以下のような形になる[1]。
package main
import (
"fmt"
"os"
"os/signal"
"syscall"
)
func main() {
// OSシグナルを受け取るためのチャンネルを作成
signalChannel := make(chan os.Signal, 1)
// sigChannelに取り込むOSシグナルの種別を指定
signal.Notify(signalChannel, syscall.SIGKILL, syscall.SIGTERM)
for {
// OSシグナルを受け取ったら処理をする
sig := <-signalChannel
fmt.Printf("シグナルを受け取った: %s\n", sig)
if sig == syscall.SIGTERM {
fmt.Printf("SIGTERMを受け取ったのでここでグレースフルなシャットダウンのための準備をする\n")
}
}
}
signalChannel := make(chan os.Signal, 1)
でOSシグナルを受け取るためのチャネルを作成する。
signal.Notify(signalChannel, syscall.SIGKILL, syscall.SIGTERM)
でsignalChannelが受け取るOSシグナルを指定する。
ここでは、syscall.SIGKILL
を指定しているが、SIGKILLは流石に対応できない模様(後述)
for
文の中のsig := <-signalChannel
でOSシグナルの取得を待受ける。
OSシグナルを受け取ったら後段の処理に進む。
動作確認
kill コマンドのプロセスID
とすることでSIGTERM
シグナルを送ることができる。
送った場合のコマンドの挙動としては次のようになる[2]。
$ ./os_signal
シグナルを受け取った: terminated
SIGTERMを受け取ったのでここでグレースフルなシャットダウンのための準備をする
kill -9 コマンドのプロセスID
でSIGKILL
シグナルを送ると次のようになる。
(Ubuntu 22.04の場合)
$ ./os_signal
強制終了
シグナルを受け取った処理云々なく、問答無用でプロセスが殺される。
他の処理をしたい場合は?
たとえばWebサーバーを立ち上げつつ、OSシグナルを受け取りたい場合はどうしたら良いか?
goルーチンをシグナルの受取前に立ち上げてその中でwebサーバーを立ち上げる形とするのが現実的な対応。
その際は、丁寧に対応するならSIGTERM
を受け取ったタイミングで、Webサーバーをシャットダウンする処理などが必要になる。
-
https://github.com/Fufuhu/os_signal にコードそのものは公開している。 ↩︎
-
go buildして生成されるバイナリでないと意図した動作にはならない。go runはNG ↩︎
Discussion