创建含重复字段的gRPC客户端请求时触发无效内存地址错误排查
问题:gRPC服务端空指针解引用错误(重复字段请求场景)
错误信息
panic: runtime error: invalid memory address or nil pointer dereference [signal SIGSEGV: segmentation violation code=0x1 addr=0x20 pc=0x165cf0e] goroutine 40 [running]: main.(*Server).RunCommands(0x172b960?, {0x18be540?, 0xc00031c030?}, 0x107a360?) <autogenerated>:1 +0x2e github.com/mygithub/some-package/proto._ExecuterService_RunCommands_Handler({0x1701a20?, 0xc0002430d0}, {0x18be540, 0xc00031c030}, 0xc000318070, 0x0) ..../executer_grpc.pb.go:93 +0x170
对应executer_grpc.pb.go第93行代码:
in := new(CommandsRequest) return srv.(ExecuterServiceServer).RunCommands(ctx, in)
相关代码
服务端代码(grpc_server.go)
package main import ( "fmt" pb "github.com/mygithub/some-package/proto" "google.golang.org/grpc" ) type Server struct { pb.ExecuterServiceServer } func startServer() { addr := GetAgentAddress() lis, err := net.Listen("tcp", addr) if err != nil { log.Fatalf("Faild to listen on %v\n", err) } s := grpc.NewServer() pb.RegisterExecuterServiceServer(s, &Server{}) if err = s.Serve(lis); err != nil { log.Fatalf("Failed to serve: %v\n", err) } }
客户端代码(grpc_client.go)
package main import ( "context" "encoding/json" "fmt" v1 "github.com/mygithub/some-package/api/v1" pb "github.com/mygithub/some-package/proto" "google.golang.org/grpc" "google.golang.org/grpc/credentials/insecure" ) func sendCommandsWithClient() (string, error) { command := v1.CommandSpec{ BashCommand: "ls", Async: false, } addr := "localhost:4000" conn, err := grpc.Dial(addr, grpc.WithTransportCredentials(insecure.NewCredentials())) if err != nil { return "", err } defer conn.Close() client := pb.NewExecuterServiceClient(conn) if err != nil { return "", err } commandRequest := &pb.CommandRequest{ BashCommand: command.BashCommand, Slug: "test", Async: command.Async, } req := &pb.CommandsRequest{} req.Commands = append(req.Commands, commandRequest) aAsJsonString, _ := json.Marshal(req) fmt.Println(string(aAsJsonString)) res, err := client.RunCommands(context.Background(), req) if err != nil { return "", err } return res.Content, nil } func main() { _, err := sendCommandsWithClient() if err != nil { return } }
RunCommands方法实现
func (s *Server) RunCommands(ctx context.Context, in *pb.CommandsRequest) (*pb.CommandsResponse, error) { log.Println("test") command := v1.CommandSpec{BashCommand: "ls", Slug: "test", Async: true} CommandsQueue := append(CommandsQueue, command) size := len(CommandsQueue) return &pb.CommandsResponse{Status: 0, Content: strconv.Itoa(size)}, nil }
问题根源与解决方案
问题根源
服务端的Server结构体错误嵌入了pb.ExecuterServiceServer接口:
type Server struct { pb.ExecuterServiceServer }
初始化&Server{}时,嵌入的接口字段默认是nil。gRPC框架调用方法时会优先尝试调用该嵌入接口的RunCommands方法,因接口实例为nil,直接触发空指针解引用错误。
实际上你的Server结构体已经通过自定义的RunCommands方法实现了pb.ExecuterServiceServer接口,完全不需要嵌入该接口。
解决方案
修改服务端的Server结构体定义,移除嵌入的接口:
type Server struct { // 可添加自定义业务字段,无需嵌入ExecuterServiceServer接口 }
修改后,pb.RegisterExecuterServiceServer(s, &Server{})会正确识别Server结构体实现了接口方法,调用时直接执行你自定义的RunCommands函数,不会再触发空指针错误。
客户端代码无问题:req.Commands = append(req.Commands, commandRequest)可正常处理nil切片(proto3重复字段默认初始化为nil,append会自动创建新切片)。
环境信息:go1.18.2 darwin/amd64,protoc proto3
内容的提问来源于stack exchange,提问作者DevFromI
相关产品推荐
相关产品推荐

