Skip to content

rpc error: code = canceled desc = context canceled

错误说明

调用rpc接口时上下文被取消,报错。

场景复现

rpc方法中,在协程调用了另一个rpc方法,并将主协程context作为参数传递。当主协程结束之后,context被取消,子协程中调用rpc失败。

错误代码

go
// rpc方法
func (s *Server) Find(ctx context.Context, request *pb.FindRequest) (result *pb.FindReply, err error) {
	result = &pb.FindReply{}
	// ....
    // ....
    go FindUser(ctx)
	return
}

func FindUser(ctx context.Context) {
    rpcClient.Query(ctx)
    // ...
}

处理办法

  • 法1: 同步处理,多协程操作使用sync.WaitGroup异步等待。
  • 法2: 新建独立的上下文,内容和源context保持一致
go
package user

import (
	pb "grpc/pb"
	"context"
	"time"

	"google.golang.org/grpc/metadata"
)

type ContextKey string
const ContextReqUUid     = "uuid"
const ContextReqUUidKey  = ContextKey(ContextReqUUid)

func (s *Server) Find(originalCtx context.Context, request *pb.FindRequest) (result *pb.FindReply, err error) {
	uuid := GetUuid(originalCtx)
	go func( uuid string) {
		// 创建基础 context
		ctx := context.Background()
		ctx, cancel := context.WithTimeout(ctx, 10*time.Minute)
		defer cancel()

		// 同时设置 context value 和 outgoing metadata
		ctx = context.WithValue(ctx, ContextReqUUidKey, []string{uuid})

		// 创建 outgoing metadata 用于 gRPC 请求
		md := metadata.New(map[string]string{
			ContextReqUUid:     uuid,
		})
		ctx = metadata.NewOutgoingContext(ctx, md) 
        FindUser(ctx)
		// ...
	}(uuid)
	result = &pb.FindReply{}
	return
}

// GetUuid ...
func GetUuid(ctx context.Context) string {
	md, ok := metadata.FromIncomingContext(ctx)
	if !ok {
		md = metadata.MD{}
	}
	uuids := md.Get(ContextReqUUid)
	if len(uuids) == 0 {
		return ""
	}
	return uuids[0]
}

推荐使用法2, 这样即使因为web服务器超时,也不影响我们的协程请求。