go-grpc-middleware/recoveryを利用してpanicから回復する
goでgRPCサーバ実装する際に、panic起きた際に落ちずに回復させる方法です。 go-grpc-middlewareのgrpc_recoveryを利用します。
事前準備
protoとgo-grpc-middlewareのインストール
$ go get github.com/golang/protobuf/proto $ go get github.com/grpc-ecosystem/go-grpc-middleware
grpc_recoveryを使わない場合
こちらをご利用ください。
$ git clone https://github.com/ybalexdp/sample-grpc $ cd schema $ protoc --go_out=plugins=grpc:../pb sample.proto
panicを起こす処理を追記
service/service_sample.go
func (s *SampleService) GetSample(ctx context.Context, message *pb.GetSampleRequest) (*pb.SampleResponse, error) { if message.Name == "panic" { panic("failed") return nil, grpc.Errorf(codes.Internal, "Unexpected error") } return &pb.SampleResponse{ Message: "Hello :" + message.Name, }, nil }
サーバ起動
$ cd .. $ go run main.go
panicを起こしてみる
$ evans --path schema --port 2007 sample.proto ______ | ____| | |__ __ __ __ _ _ __ ___ | __| \ \ / / / _. | | '_ \ / __| | |____ \ V / | (_| | | | | | \__ \ |______| \_/ \__,_| |_| |_| |___/ more expressive universal gRPC client pb.Sample@127.0.0.1:2007> call GetSample name (TYPE_STRING) => panic command call: failed to send a request: rpc error: code = Unavailable desc = transport is closing
エラーです。
その後再度API叩いてみると、サーバが落ちていることが確認できます。
pb.Sample@127.0.0.1:2007> call GetSample name (TYPE_STRING) => test command call: failed to send a request: rpc error: code = Unavailable desc = all SubConns are in TransientFailure, latest connection error: connection error: desc = "transport: Error while dialing dial tcp 127.0.0.1:2007: connect: connection refused"
サーバ側
下記のように落ちています。
$ go run main.go (git)-[master] panic: failed goroutine 4 [running]: github.com/ybalexdp/sample-grpc/service.(*SampleService).GetSample(0x1843840, 0x15263a0, 0xc0001681b0, 0xc0001681e0, 0x1843840, 0xc000168150, 0x142b5e0) /Users/ybalexdp/go/src/github.com/ybalexdp/sample-grpc/service/service_sample.go:16 +0xf0 github.com/ybalexdp/sample-grpc/pb._Sample_GetSample_Handler(0x14224a0, 0x1843840, 0x15263a0, 0xc0001681b0, 0xc000164300, 0x0, 0x0, 0x0, 0xc000026128, 0x7) /Users/ybalexdp/go/src/github.com/ybalexdp/sample-grpc/pb/sample.pb.go:180 +0x23e google.golang.org/grpc.(*Server).processUnaryRPC(0xc000084900, 0x1528160, 0xc000084d80, 0xc00018e000, 0xc0000a3e30, 0x1816e90, 0x0, 0x0, 0x0) /Users/ybalexdp/go/src/google.golang.org/grpc/server.go:1007 +0x485 google.golang.org/grpc.(*Server).handleStream(0xc000084900, 0x1528160, 0xc000084d80, 0xc00018e000, 0x0) /Users/ybalexdp/go/src/google.golang.org/grpc/server.go:1287 +0xdf9 google.golang.org/grpc.(*Server).serveStreams.func1.1(0xc0000a8890, 0xc000084900, 0x1528160, 0xc000084d80, 0xc00018e000) /Users/ybalexdp/go/src/google.golang.org/grpc/server.go:722 +0x9f created by google.golang.org/grpc.(*Server).serveStreams.func1 /Users/ybalexdp/go/src/google.golang.org/grpc/server.go:720 +0xa1 exit status 2
grpc_recoveryを使ってサーバを落とさせない
ソースもあげておきます。
go run main.go
でgRPCのサーバが立ち上がります。
grpc_recoveryを実装する
main.go
package main import ( "fmt" "log" "net" grpc_middleware "github.com/grpc-ecosystem/go-grpc-middleware" grpc_recovery "github.com/grpc-ecosystem/go-grpc-middleware/recovery" "github.com/ybalexdp/sample-grpc_recovery/pb" "github.com/ybalexdp/sample-grpc_recovery/service" "google.golang.org/grpc" "google.golang.org/grpc/codes" ) func main() { opts := []grpc_recovery.Option{ grpc_recovery.WithRecoveryHandler(recoveryFunc), } server := grpc.NewServer( grpc_middleware.WithUnaryServerChain( grpc_recovery.UnaryServerInterceptor(opts...), ), ) listenPort, err := net.Listen("tcp", ":2007") if err != nil { log.Fatalln(err) } sampleService := &service.SampleService{} pb.RegisterSampleServer(server, sampleService) server.Serve(listenPort) } func recoveryFunc(p interface{}) error { fmt.Printf("p: %+v\n", p) return grpc.Errorf(codes.Internal, "Unexpected error") }
サーバ起動
$ cd .. $ go run main.go
panicを起こした後、再度APIを実行する
pb.Sample@127.0.0.1:2007> call GetSample name (TYPE_STRING) => panic command call: failed to send a request: rpc error: code = Internal desc = Unexpected error pb.Sample@127.0.0.1:2007> call GetSample name (TYPE_STRING) => ybalexdp { "message": "Hello :ybalexdp" }
サーバが落ちていないことが確認できました。
サーバ側
実装したエラー出力のみで、サーバは落ちていないことが確認できます。
$ go run main.go p: failed
参考
go-grpc-middlewareを一通り試してみる - Qiita
GoのgRPC ServerのInterceptor(recovery/auth/zap/prometheus) - sambaiz-net