When writing a StreamClientInterceptor function, what's the best way to determine when an invoker finishes the RPC? This is straightforward enough with unary interceptors or on the server-side where you're passed a handler that performs the RPC, but it's not clear how best to do this on the client-side where you return a ClientStream that the invoker then interacts with.
One use case for this is instrumenting OpenTracing, where the goal is to start and finish a span to mark the beginning and end of the RPC.
A strategy I'm looking into is having the stream interceptor return a decorated ClientStream. This new ClientStream considers the RPC to have completed if any of the interface methods Header
, CloseSend
, SendMsg
, RecvMsg
return an error or if the Context
is cancelled. Additionally, it adds this logic to RecvMsg
:
func (cs *DecoratedClientStream) RecvMsg(m interface{}) error {
err := cs.ClientStream.RecvMsg(m)
if err == io.EOF {
// Consider the RPC as complete
return err
} else if err != nil {
// Consider the RPC as complete
return err
}
if !cs.isResponseStreaming {
// Consider the RPC as complete
}
return err
}
It would work in most cases, but my understanding is that an invoker isn't required to call Recv
if it knows the result will be io.EOF
(See Are you required to call Recv until you get io.EOF when interacting with grpc.ClientStreams?), so it wouldn't work in all cases. Is there a better way to accomplish this?