为了账号安全,请及时绑定邮箱和手机立即绑定

使用 WithBlock() 选项创建 gRPC 客户端连接到异步启动的 gRPC

使用 WithBlock() 选项创建 gRPC 客户端连接到异步启动的 gRPC

Go
眼眸繁星 2022-06-13 16:10:17
我想编写一个单元测试,在其中运行一个临时 gRPC 服务器,该服务器在测试中的一个单独的 Goroutine 中启动,并在测试运行后停止。为此,我尝试将本教程(https://grpc.io/docs/languages/go/quickstart/)中的“Hello, world”示例改编为其中的一个,而不是服务器和客户端main.gos,有一个测试函数异步启动服务器,随后使用该grpc.WithBlock()选项建立客户端连接。我已将简化示例放在此存储库中,https://github.com/kurtpeek/grpc-helloworld;这是main_test.go:package mainimport (    "context"    "fmt"    "log"    "net"    "testing"    "time"    "github.com/stretchr/testify/require"    "google.golang.org/grpc"    "google.golang.org/grpc/examples/helloworld/helloworld")const (    port = ":50051")type server struct {    helloworld.UnimplementedGreeterServer}func (s *server) SayHello(ctx context.Context, in *helloworld.HelloRequest) (*helloworld.HelloReply, error) {    log.Printf("Received: %v", in.GetName())    return &helloworld.HelloReply{Message: "Hello " + in.GetName()}, nil}func TestHelloWorld(t *testing.T) {    lis, err := net.Listen("tcp", port)    require.NoError(t, err)    s := grpc.NewServer()    helloworld.RegisterGreeterServer(s, &server{})    go s.Serve(lis)    defer s.Stop()    log.Println("Dialing gRPC server...")    conn, err := grpc.Dial(fmt.Sprintf("localhost:%s", port), grpc.WithInsecure(), grpc.WithBlock())    require.NoError(t, err)    defer conn.Close()    c := helloworld.NewGreeterClient(conn)    ctx, cancel := context.WithTimeout(context.Background(), time.Second)    defer cancel()    log.Println("Making gRPC request...")    r, err := c.SayHello(ctx, &helloworld.HelloRequest{Name: "John Doe"})    require.NoError(t, err)    log.Printf("Greeting: %s", r.GetMessage())}问题是当我运行这个测试时,它会超时:> go test -timeout 10s ./... -v=== RUN   TestHelloWorld2020/06/30 11:17:45 Dialing gRPC server...panic: test timed out after 10s我无法理解为什么没有建立连接?在我看来,服务器已正确启动...
查看完整描述

1 回答

?
繁星coding

TA贡献1797条经验 获得超4个赞

您在此处发布的代码似乎有错字:


fmt.Sprintf("localhost:%s", port)


如果我在没有该grpc.WithBlock()选项的情况下运行您的测试功能,c.SayHello则会出现以下错误:


rpc error: code = Unavailable desc = connection error: desc = "transport: Error while dialing dial tcp: address localhost::50051: too many colons in address"

罪魁祸首似乎是localhost::50051


从const声明中删除多余的冒号后(或从 fmt.Sprintf("localhost:%s", port),如果您愿意),测试通过。


const (

    port = "50051" // without the colon

)

输出:


2020/06/30 23:59:01 Dialing gRPC server...

2020/06/30 23:59:01 Making gRPC request...

2020/06/30 23:59:01 Received: John Doe

2020/06/30 23:59:01 Greeting: Hello John Doe

但是,从文档grpc.WithBlock()


如果没有这个,Dial 会立即返回并在后台连接服务器。


因此,使用此选项,应立即从grpc.Dial调用返回任何连接错误:


conn, err := grpc.Dial("bad connection string", grpc.WithBlock()) // can't connect

if err != nil {

    panic(err) // should panic, right?

}

那么为什么你的代码会挂起?


通过查看grpc包的源代码(我针对 构建了测试v1.30.0):


    // A blocking dial blocks until the clientConn is ready.

    if cc.dopts.block {

        for {

            s := cc.GetState()

            if s == connectivity.Ready {

                break

            } else if cc.dopts.copts.FailOnNonTempDialError && s == connectivity.TransientFailure {

                if err = cc.connectionError(); err != nil {

                    terr, ok := err.(interface {

                        Temporary() bool

                    })

                    if ok && !terr.Temporary() {

                        return nil, err

                    }

                }

            }

            if !cc.WaitForStateChange(ctx, s) {

                // ctx got timeout or canceled.

                if err = cc.connectionError(); err != nil && cc.dopts.returnLastError {

                    return nil, err

                }

                return nil, ctx.Err()

            }

        }


所以s此时确实处于TransientFailure状态,但FailOnNonTempDialError选项默认为false,并且WaitForStateChange在上下文过期时为 false ,这不会发生,因为Dial与后台上下文一起运行:


// Dial creates a client connection to the given target.

func Dial(target string, opts ...DialOption) (*ClientConn, error) {

    return DialContext(context.Background(), target, opts...)

}

在这一点上,我不知道这是否是预期的行为,因为其中一些 APIv1.30.0被标记为实验性的。


无论如何,最终为了确保你捕捉到这种错误,Dial你也可以将你的代码重写为:


    conn, err := grpc.Dial(

        "localhost:50051", 

        grpc.WithInsecure(),

        grpc.FailOnNonTempDialError(true),

        grpc.WithBlock(), 

    )

如果连接字符串错误,则会立即失败并显示相应的错误消息。


查看完整回答
反对 回复 2022-06-13
  • 1 回答
  • 0 关注
  • 334 浏览
慕课专栏
更多

添加回答

举报

0/150
提交
取消
微信客服

购课补贴
联系客服咨询优惠详情

帮助反馈 APP下载

慕课网APP
您的移动学习伙伴

公众号

扫描二维码
关注慕课网微信公众号