Goのpanicとrecover

(2017-01-17)

panic

https://golang.org/pkg/builtin/#panic

panicは現在のgoroutineの通常の実行を停止する組み込み関数。 index out of rangeinvalid 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/a96c564fc34b3fcbc5a1a67eeb9402243cdac6b2/middleware/recover.go#L66

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してるのでここでひっかかる(レスポンスは返らない)。

https://github.com/golang/go/blob/b2a3b54b9520ce869d79ac8bce836a540ba45d09/src/net/http/server.go#L2625

https://github.com/golang/go/blob/b2a3b54b9520ce869d79ac8bce836a540ba45d09/src/net/http/server.go#L1718

echo: http: panic serving [::1]:53992: AA

参考

Defer, Panic, and Recover - The Go Blog