well-typed / grapesy

Native Haskell gRPC client and server based on `http2`
Other
31 stars 4 forks source link

Final received element is not marked as final #114

Closed edsko closed 3 weeks ago

edsko commented 2 months ago

Background

(This is copied from Network.GRPC.Client.)

Network.GRPC.Common.StreamElem allows to mark the final message as final when it is sent (Network.GRPC.Common.FinalElem), or retroactively indicate that the previous message was in fact final (Network.GRPC.Common.NoMoreElems). The reason for this is technical in nature.

Suppose we are doing a grpc+proto non-streaming RPC call. The input message from the client to the server will be sent over one or more HTTP2 DATA frames (chunks of the input). The server will expect the last of those frames to be marked as END_STREAM. The HTTP2 specification does allow sending an separate empty DATA frame with the END_STREAM flag set to indicate no further data is coming, but not all gRPC servers will wait for this, and might either think that the client is broken and disconnect, or might send the client a RST_STREAM frame to force it to close the stream. To avoid problems, therefore, it is better to mark the final DATA frame as END_STREAM; in order to be able to do that, sendInput needs to know whether an input is the final one. It is therefore better to use FinalElem instead of NoMoreElems for outgoing messages, if possible.

For incoming messages the situation is different. Now we do expect HTTP trailers (final metadata), which means that we cannot tell from DATA frames alone if we have received the last message: it will be the frame containing the trailers that is marked as END_STREAM, with no indication on the data frame just before it that it was the last one. We cannot wait for the next frame to come in, because that would be a blocking call (we might have to wait for the next TCP packet), and if the output was not the last one, we would unnecessarily delay making the output we already received available to the client code. Typically therefore clients will receive a StreamElem followed by NoMoreElems.

Of course, for a given RPC and its associated communication pattern we may know whether it any given message was the last; in the example above of a non-streaming grpc+proto RPC call, we only expect a single output. In this case the client can (and should) call recvOutput again to wait for the trailers (which, amongst other things, will include the trailerGrpcStatus). The specialized functions from Network.GRPC.Client.StreamType take care of this; if these functions are not applicable, users may wish to use recvFinalOutput.

Problem

While we handle this correctly on the outgoing side, we do not do this correctly on the incoming side. The problem is when we read data from a HTTP stream, the interface that http2 (the package) provides just gives us bytestrings, with an empty bytestring indicating that there is no more input. We'd need a different API from http2 which would give us more information: "here is a chunk of data, and by the way, it's the last". (It is not correct to wait for the next bytestring, because we don't know ahead of time if that is a blocking operation or not.)

Consequences

For grapesy clients there are no consequences, as incoming data from the server is marked by the trailers anyway (as described above under Background).

For grapesy servers the consequence is that the server needs to do an additional recvInput before it knows for sure that an input was the last, but in most cases this is unproblematic; either the server knows to expect a single input only anyway, or else it doesn't know ahead of time how many inputs to expect and it reads until the client indicates that there are no more inputs. It is however conceivable that for some specific applications this is problematic.

The problem is most severe for grapesy proxies. Suppose we have a grapesy proxy listening to a client on one side and forwarding everything to a server on the other. If that server is an example of the kind of servers that must be told when they receive the last message that it is indeed the last, then the proxy is in trouble. Even if the client that the proxy is listening to would tell the proxy that a particular message is the last one as they send it, the proxy will not notice and will not be able to pass on this information to the server on the other side.