1
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 1 year has passed since last update.

【Golang】recoverについて

Last updated at Posted at 2022-06-29

概要

リリース前のプロジェクトのソースコードを見ていく中で、ふとrecoverが入っていないことに気づきました。recoverについての理解が浅く、どんな挙動をするのか、何のためにrecoverするのか理解できていなかったので備忘としてまとめます。
golangのフレームワークのリファレンスなどに記載されているから、なんとなくrecoverを入れている、使い方や用途は実はいまいちわかっていない方などに読んでいただけますと幸いです。

recoverでできること

  • panicが発生したとしてもプログラムを終了せずに復帰できます。
  • 本当にシンプルにこれだけです笑

疑問

ここで疑問に思ったのがrecoverの使い方でした。
例えばサーバーでpanicが発生したとして、panic=致命的なエラーなのでこれをrecoverしたところで全ての処理に失敗するのでは・・?
今はどこも実環境でコンテナ技術を用いたサーバーを構築していると思うので、いっそrecoverを入れずにサーバーを落としてしまって、リクエストを処理の成功するコンテナに誘導するほうがいいのでは・・?

結論

上記の疑問は間違っていました。recoverは入れるべきだと思います。
effective goを読むと下記のように記載されています。

原文
One application of recover is to shut down a failing goroutine inside a server without killing the other executing goroutines.

日本語訳
recoverの応用例としては、サーバー内で故障したゴルーチンを、他の実行中のゴルーチンを殺すことなくシャットダウンすることが挙げられます。

つまり、サーバーが複数のリクエストを処理していると仮定した時に、golangではこの複数の並行処理をgoroutineを使って捌きます。
recoverを入れておくことで、仮にどれか一つのgoroutineでpanicが発生したとしても他のgoroutineは生き続けることができる、というのがrecoverの代表的な使い方ということになります。
よくよく考えたら疑問で言っていたパターンだと、panicが発生した時点で他のgoroutineも共倒れで死んでしまいますよね。。。

実装

以上を踏まえて実案件のコードでも実装をしてみました。(だいぶ簡略化しています)

main.go

func main() {
	// DBのコネクションの作成の独自関数
	conn, err := repository.InitDBConnection("qiita")
	if err != nil {
		log.Fatalf("db connection failed: %v", err)
	}

	s := grpc.NewServer(
		grpc.UnaryInterceptor(
			grpc_middleware.ChainUnaryServer(
				middleware.Recover(),
			),
		),
	)

	rpc.RegisterServices(s, conn)

	lis, err := net.Listen("tcp", ":50000")
	if err != nil {
		log.Fatalf("failed to : %v", err)
	}
	if err := s.Serve(lis); err != nil {
		log.Fatalf("failed to serve: %v", err)
	}
}

recover.go

func Recover() grpc.UnaryServerInterceptor {
	return func(
		ctx context.Context,
		req interface{},
		info *grpc.UnaryServerInfo,
		handler grpc.UnaryHandler,
	) (_ interface{}, err error) {
		defer func() {
			if r := recover(); r != nil {
				fmt.Printf("%v", r)
				err = status.Error(codes.Internal, "unexpected error")
			}
		}()

		resp, err := handler(ctx, req)
		return resp, err
	}
}

recoverはmiddlewareとして使用しています。これによりリクエストごとにrecoverがdeferで呼び出されます。
recover.go内ではエラーの内容を標準出力しログにのこし、レスポンスとして返す情報としてはHTTPステータス500unexpected errorとして返すようにしました。
これでサーバーを落とさずに、かつ根本原因はログに残しつつクライアントには内部の事情を知られないようなレスポンスを返すことができます。

さいごに

トレタでは一緒に開発する仲間を募集しています。

興味がある方は是非カジュアル面談へお越しください!

1
0
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
1
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?