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:
server.go
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
}
// ...
}
rpc_util.go
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.