如何在客户端断开连接时正确使用ctx.Done()?

时间:2017-10-20 05:31:35

标签: go grpc channel

如果客户端因网络错误而断开连接,则服务器必须在我的情况下关闭pub / sub连接。我知道ctx.Done()函数,但在我的情况下不知道如何正确使用它。有人可以解释一下吗?

grpc-go:1.7.0

go go go.8.4.4

func (a *API) Notifications(in *empty.Empty, stream pb.Service_NotificationsServer) error {
    ctx := stream.Context()
    _, ok := user.FromContext(ctx)
    if !ok {
        return grpc.Errorf(codes.Unauthenticated, "user not found")
    }

    pubsub := a.redisClient.Subscribe("notifications")
    defer pubsub.Close()

    for {
        msg, err := pubsub.ReceiveMessage()
        if err != nil {
            grpclog.Warningf("Notifications: pubsub error: %v", err)
            return grpc.Errorf(codes.Internal, "pubsub error %v", err)
        }

        notification := &pb.Notification{}
        err = json.Unmarshal([]byte(msg.Payload), notification)
        if err != nil {
            grpclog.Warningf("Notifications: parse error: %v", err)
            continue
        }
        if err := stream.Send(notification); err != nil {
            grpclog.Warningf("Notifications: %v", err)
            return err
        }
        grpclog.Infof("Notifications: send msg %v", notification)
    }
}

2 个答案:

答案 0 :(得分:1)

您可以使用select。而不是正常从函数获取数据,使用通道获取数据和go例程来处理它。 有点像这样:

func (a *API) Notifications(in *empty.Empty, stream 
    pb.Service_NotificationsServer) error {
    ctx := stream.Context()
    _, ok := user.FromContext(ctx)
    if !ok {
        return grpc.Errorf(codes.Unauthenticated, "user not found")
    }

    pubsub := a.redisClient.Subscribe("notifications")
    defer pubsub.Close()

    // I can not build the code, so I assume the msg in your code Message struct
    c := make(chan Message)
    go func() {
        for {
            msg, err := pubsub.ReceiveMessage()
            if err != nil {
                grpclog.Warningf("Notifications: pubsub error: %v", err)
                close(c)
                return grpc.Errorf(codes.Internal, "pubsub error %v", err)
            }
            c<- msg
        }
    }()

    for {
        select {
            case msg, ok  := <-c:
                if !ok {
                    // channel is closed handle it
                }
                notification := &pb.Notification{}
                err = json.Unmarshal([]byte(msg.Payload), notification)
                if err != nil {
                    grpclog.Warningf("Notifications: parse error: %v", err)
                    continue
                }
                if err := stream.Send(notification); err != nil {
                    grpclog.Warningf("Notifications: %v", err)
                    return err
                }
                grpclog.Infof("Notifications: send msg %v", notification)
            case <- ctx.Done():
                // do exit logic. some how close the pubsub, so next 
                // ReceiveMessage() return an error
                // if forget to do that the go routine runs for ever 
                // until the end of main(), which I think its not what you wanted
                pubsub.Close() // Its just pseudo code
                return
        }
    }
}

从频道中读取消息(我假设类型为Message),并使用select的幂。

在这种情况下另外两件事:

  1. 确保完成此功能后,go例程结束。我无法猜测,因为我不知道代码,但我认为有Close()方法可以关闭pubsub,以便下一个ReceiveMessage返回错误。 (我看到推迟做我希望的工作)

  2. 如果ReceiveMessage之前ctx.Done出现错误,您可以关闭频道然后中断循环。

答案 1 :(得分:0)

您应该从调用者函数(或可以访问上下文的任何位置)取消上下文,并对Done()检查select语句执行适当的操作。

  

完成提供用于选择语句

     

当代表此上下文完成的工作应该被取消时,完成返回已关闭的频道。如果永远不能取消此上下文,则可以返回nil。对Done的连续调用返回相同的值。

  

WithCancel返回父级的副本,其中包含新的完成频道。当返回的取消函数被调用或父上下文的完成频道关闭时(以先发生者为准),返回的上下文的完成频道将被关闭。

     

取消此上下文会释放与其关联的资源,因此代码应在此上下文中运行的操作完成后立即调用cancel。

go func() {
    for {
        select {
        case <-ctx.Done():
            return // returning not to leak the goroutine
        case dst <- n:
            n++
        }
    }
}()