Reputation: 22097
In the following gRPC
-client code, is the second if
necessary?
status, err := cli.GetStatus(ctx, &empty.Empty{})
if err != nil {
return err
}
if status == nil {
// this should NEVER happen - right?
return fmt.Errorf("nil Status result returned")
}
Intuitively, one should always check for nil in go
just in case.
However, there is a runtime check to catch any client-to-server nil
usage e.g.
status, err := cli.GetStatus(ctx, nil) // <- runtime error
if err != nil {
// "rpc error: code = Internal desc = grpc: error while marshaling: proto: Marshal called with nil"
return err
}
So is there a similar server-to-client runtime guarantee, and thus remove the need for a status == nil
check?
Upvotes: 6
Views: 6402
Reputation: 22097
Investigating further with a contrived server example:
func (s *mygRPC) GetStatus(context.Context, *empty.Empty) (*pb.Status, error) {
log.Println("cli: GetStatus()")
//return &pb.Status{}, nil
return nil, nil // <- can server return a nil status message (with nil error)
}
and testing client/server reactions:
CLIENT:
ERROR: rpc error: code = Internal desc = grpc: error while marshaling: proto: Marshal called with nil
SERVER:
2019/05/14 16:09:50 cli: GetStatus()
ERROR: 2019/05/14 16:09:50 grpc: server failed to encode response: rpc error: code = Internal desc = grpc: error while marshaling: proto: Marshal called with nil
So even if one wanted to legitimately return a nil value, the gRPC
transport will not allow it.
Note: the server-side code is still executed - as expected - but as far as the client is concerned, the gRPC
call failed.
Conclusion: a valid (err==nil
) server response will always return a valid (non-nil
) message.
EDIT:
Inspecting the gRPC
source reveals where a nil
message is caught:
func (s *Server) sendResponse(t transport.ServerTransport, stream *transport.Stream, msg interface{}, cp Compressor, opts *transport.Options, comp encoding.Compressor) error {
data, err := encode(s.getCodec(stream.ContentSubtype()), msg)
if err != nil {
grpclog.Errorln("grpc: server failed to encode response: ", err)
return err
}
// ...
}
func encode(c baseCodec, msg interface{}) ([]byte, error) {
if msg == nil { // NOTE: typed nils will not be caught by this check
return nil, nil
}
b, err := c.Marshal(msg)
if err != nil {
return nil, status.Errorf(codes.Internal, "grpc: error while marshaling: %v", err.Error())
}
// ...
}
The comment in this line is key:
if msg == nil { // NOTE: typed nils will not be caught by this check }
So if one were to use reflect on our typed-nil, reflect.ValueOf(msg).IsNil()
would return true
. The following c.Marshal(msg)
errors - and the call fails to send a message response to the client.
Upvotes: 8