Goのpanicとrecover
golangpanic
https://golang.org/pkg/builtin/#panic
panicは現在のgoroutineの通常の実行を停止する組み込み関数。 index out of rangeや invalid memory address or nil pointer dereference のときなどでも呼ばれる。
deferを実行して呼び出し元に戻り、panicの実行->deferの実行->呼び出し元に戻る、を繰り返して 最後まで戻ったらプログラムを終了し、panicに渡した引数と共にエラーをレポートする。
func main() {
a()
}
func a() {
defer fmt.Println("a")
b()
fmt.Println("a2")
}
func b() {
defer fmt.Println("b1")
panic("b2")
defer fmt.Println("b3")
}
b1
a
panic: b2
goroutine 1 [running]:
panic(0x89840, 0xc42000a2c0)
/*****/libexec/src/runtime/panic.go:500 +0x1a1
main.b()
/*****/main.go:19 +0x107
main.a()
/*****/main.go:13 +0xce
main.main()
/*****/main.go:8 +0x14
exit status 2
recover
https://golang.org/pkg/builtin/#recover
deferで呼ぶことによってpanicを停止させることができる組み込み関数。 panicの引数に渡した値を取得できる。
func main() {
fmt.Println(a())
fmt.Println("main")
}
func a() (ret string) {
defer func() {
if err := recover(); err != nil {
fmt.Println("recover ->", err)
ret = "panicked"
}
}()
b()
fmt.Println("a2")
return "ok"
}
func b() {
defer fmt.Println("b1")
panic("b2")
defer fmt.Println("b3")
}
b1
recover -> b2
panicked
main
通常はpanicもrecoverもあまり使わず、errorを返すことでハンドリングする。
ではどんな時に使われるかというと、例えばWebフレームワークechoのRecover middlewareは panicをrecoverしてinternal server errorとしてレスポンスを返すようにしている。
https://github.com/labstack/echo/blob/54fb1015c1a51aed1c8e5ef6bf9e643b1a079acb/context.go#L525
https://github.com/labstack/echo/blob/b2c623b07dd1362011f2677147ffbbe48ea3b178/echo.go#L284
func main() {
// Echo instance
e := echo.New()
// Middleware
e.Use(middleware.Logger())
e.Use(middleware.Recover())
// Route => handler
e.GET("/", func(c echo.Context) error {
panic("fail")
return c.String(http.StatusOK, "Hello, World!\n")
})
// Start server
e.Logger.Fatal(e.Start(":1323"))
}
{"message":"Internal Server Error"}
ではRecover middlewareを使わなかったらアプリケーションが終了するかというと、 net/httpのserveでもrecoverしてるのでここでひっかかる(レスポンスは返らない)。
echo: http: panic serving [::1]:53992: AA