GolangでSIGKILL、SIGSTOPはcatchできない
GolangでSIGKILLやSITGTOPをcatchしようとしてしまったときのことを書いた.
やらかしたこと
Goで常駐プロセスを動かすときに、SIGNALをトラップしてgracefulにプロセスを止めることができるように以下のようなプログラムを書くことがあった.
go code snippet start
package main
import (
"context"
"fmt"
"os"
"os/signal"
"syscall"
"time"
)
func main() {
c := make(chan os.Signal)
signal.Notify(c, syscall.SIGINT)
ctx, cancel := context.WithCancel(context.Background())
go func() {
sig := <-c
fmt.Printf("Got %s signal. Aborting...\n", sig)
cancel()
}()
doSomething(ctx)
}
func doSomething(ctx context.Context) {
defer fmt.Println("End of doSomething func")
for {
select {
case <-time.Tick(1 * time.Second):
fmt.Println("hello")
case <-ctx.Done():
return
}
}
}go code snippet end
このプログラムを停止するには、このプロセスにSIGINTシグナルを送ればよい.
SIGKILLやSIGSTOPでもgracefulに停止できるようにしようと思って以下のように変えてみたが、これは間違いだ.
コンパイルエラーもランタイムエラーも起きないが、シグナルをcatchすることはできない.
go code snippet start
signal.Notify(c, syscall.SIGINT, syscall.SIGKILL, syscall.SIGSTOP)go code snippet end
SIGKILL, SIGSTOPはcatchできない
そもそもSIGKILLとSITSTOPは、catchできないシグナルだった…
Signal (IPC)
Goのオフィシャルドキュメントにも明記してある.
The signals SIGKILL and SIGSTOP may not be caught by a program, and therefore cannot be affected by this package.
上記のようなコードはコンパイルエラーにするべきだ、というissueが上がっていましたが、見送られたようです.
os/signal: Prevent developers from catching SIGKILL and SIGSTOP
理由は以下.
signal.Notify(c chan<- os.Signal, sig ...os.Signal)のAPIとしては正しいのでコンパイルエラーにすべきでない- このような問題の検出には、go vetが役に立つ
- Go 2だと改善する?
- ランタイムエラーとするにはerrorを返すようにAPIを変更するかpanicを起こす必要があるが、いずれも既存のコードの互換性が壊れるので厳しい
などなど
まとめ
- SIGKILLとSIGSTOPは、そもそもcatchできない.
- GoでSIGKILLとSIGSTOPをcatchするコードは、コンパイルエラーにもランタイムエラーにもならないので間違って書かないように気をつけましょう.