I have the following case. A client uploads files to a server using client-streaming (via grpc-dotnet). In some cases, a server might decide to ignore the client stream and return a response immediately. Since the client is unaware of the server's decision it continues to call RequestStream.WriteAsync() and eventually gets Grpc.Core.RpcException: 'Status(StatusCode="OK", Detail="")'
The question is: is there a graceful way to know in advance that the call is already completed without making faulty write attempts?
Skimming through the grpc-dotnet source code I managed to find a solution via a reflection AsyncClientStreamingCall.RequestStream.Call.ResponseFinished
flag but it looks even worse than handling exception with a known StatusCode.
Client pseudo code:
private static async Task CallUploadStream(GrpcService.GrpcServiceClient client)
{
using var streamingCall = client.UploadStream();
// First call
await WriteAsync();
// Delay
await Task.Delay(TimeSpan.FromSeconds(1));
// Second call
await WriteAsync(); // Grpc.Core.RpcException: 'Status(StatusCode="OK", Detail="")'
await streamingCall.RequestStream.CompleteAsync();
await streamingCall;
return;
Task WriteAsync()
{
return streamingCall.RequestStream.WriteAsync(
new UploadStreamRequest
{
Bytes = ByteString.CopyFrom(new byte[1]),
}
);
}
}
Protobuf contract:
syntax = "proto3";
package grpc.debug.contract.v1;
option csharp_namespace = "GrpcDebug.Contract";
import "google/protobuf/empty.proto";
service GrpcService {
rpc UploadStream(stream UploadStreamRequest) returns (google.protobuf.Empty);
}
message UploadStreamRequest { bytes bytes = 1; }
The server that immediately returns response:
public class GrpcServiceV1 : GrpcService.GrpcServiceBase
{
public override Task<Empty> UploadStream(IAsyncStreamReader<UploadStreamRequest> requestStream,
ServerCallContext context)
{
return Task.FromResult(new Empty());
}
}
So to be clear: is it the case that the call to WriteAsync
faults with RpcException
? You could try checking call.ResponseAsync.IsCompleted
before each write (or perhaps each bit of work to get the next chunk), however this is inherently a race condition and you must always be ready to catch the exception even if you check on the line before. If what you're emulating here is basically a Stream
, or at least: you're sending a large payload in multiple chunks, then you may also be interested in the protobuf-net.Grpc work to add binary streaming support natively; in the version on NuGet this is currently limited to servers returning a Stream
, but that's mostly because that's the scenario that I had a consumer needing urgently - the plan is to add all the direction combinations, and support for APIs other than Stream
, such as Pipe
, etc.