0️⃣

grpc-goのNewClient()を使うとbufconnを使ったテストがUnavailableでコケる件の対処法

2024/06/08に公開

TL;DR

  • grpc-go v1.64.0からDial()DialContext()がDeprecatedとなった
  • 代わりに推奨されたNewClient()を使うとbufconnを使っているテストがUnavailable(name resolver error: produced zero addresses)で落ちる
  • 初期化として resolver.SetDefaultScheme("passthrough") を突っ込むと動くよ

原因

gRPCクライアントを作成する際、従来のDial(), DialContext()ではデフォルトのネームリゾルバーは passthrough として作成されます。

https://github.com/grpc/grpc-go/blob/fa274d77904729c2893111ac292048d56dcf0bb1/clientconn.go#L219

これがNewClient()からは dns になったためです。

https://github.com/grpc/grpc-go/blob/fa274d77904729c2893111ac292048d56dcf0bb1/clientconn.go#L130

https://github.com/grpc/grpc-go/blob/fa274d77904729c2893111ac292048d56dcf0bb1/dialoptions.go#L657

bufconnは passthrough であることが前提で作られているためエラーになります。

rpc error: code = Unavailable desc = name resolver error: produced zero addresses

対処法

従来通りに戻してやれば動きます。具体的にはNewClient()より前に

resolver.SetDefaultScheme("passthrough")

の記述を入れます。もしくは引数として渡すtargetにURLスキームとして "passthrough://"+ のように手で追加しても大丈夫ですが、せっかく変更用の関数が提供されているのでデフォルトを変更する方が素直だと思います。

但しこの関数はスレッドセーフではないのでinit()に入れたりsync.Mutexやsync.Once等でレースコンディションを防止した方がよいでしょう

サンプル

従来のコード

ln := bufconn.Listen(1024 * 1024)
dialOpts := []grpc.DialOption{
	grpc.WithTransportCredentials(insecure.NewCredentials()),
	grpc.WithContextDialer(func(_ context.Context, _ string) (net.Conn, error) {
		return ln.Dial()
	}),
}
conn, _ := grpc.Dial(ln.Addr().String(), dialOpts...)

コケるコード

ln := bufconn.Listen(1024 * 1024)
dialOpts := []grpc.DialOption{
	grpc.WithTransportCredentials(insecure.NewCredentials()),
	grpc.WithContextDialer(func(_ context.Context, _ string) (net.Conn, error) {
		return ln.Dial()
	}),
}
conn, _ := grpc.NewClient(ln.Addr().String(), dialOpts...) // 単純に置き換えただけ

通るコード

ln := bufconn.Listen(1024 * 1024)
dialOpts := []grpc.DialOption{
	grpc.WithTransportCredentials(insecure.NewCredentials()),
	grpc.WithContextDialer(func(_ context.Context, _ string) (net.Conn, error) {
		return ln.Dial()
	}),
}
resolver.SetDefaultScheme("passthrough") // これで従来と同じ設定になる
conn, _ := grpc.NewClient(ln.Addr().String(), dialOpts...)

もしくは

ln := bufconn.Listen(1024 * 1024)
dialOpts := []grpc.DialOption{
	grpc.WithTransportCredentials(insecure.NewCredentials()),
	grpc.WithContextDialer(func(_ context.Context, _ string) (net.Conn, error) {
		return ln.Dial()
	}),
}
//resolver.SetDefaultScheme("passthrough")
conn, _ := grpc.NewClient("passthrough://"+ln.Addr().String(), dialOpts...) // 手でスキーム追加しても一応動く

Discussion