Search code examples
c#grpc.net-8.0deadlines

How do I set a universal deadline to gRPC clients created by the gRPC client factory?


Following along the gRPC client factory integration in .NET I can register a gRPC client like this:

builder.Services.AddGrpcClient<Greeter.GreeterClient>(o =>
{
    o.Address = new Uri("https://localhost:5001");
});

Now I would like to set a universal deadline for all calls that are made with this client. Maybe something like this:

int deadline = ReadFromConfig();

builder.Services.AddGrpcClient<Greeter.GreeterClient>(o =>
{
    o.Address = new Uri("https://localhost:5001");
    o.CallOptionsActions.Add(o => () =>
        o.CallOptions.Deadline = DateTime.UtcNow.AddSeconds(deadline))
});

However, the deadline in the call options is read-only here. It seems like I cannot set it here. Am I doing it wrong, or is it only possible to set a deadline when sending out the request?


Solution

  • Completely untested, but how about an Interceptor ?

    var channel = GrpcChannel.ForAddress("https://whatever");
    var invoker = channel.Intercept(new DeadlineInterceptor(TimeSpan.FromSeconds(10)));
    // TODO: create your client from "invoker", not "channel"
    
    
    class DeadlineInterceptor(TimeSpan deadline) : Interceptor
    {
        private void ApplyDeadline<TRequest, TResponse>(ref ClientInterceptorContext<TRequest, TResponse> context)
            where TRequest : class
            where TResponse : class
        {
            if (context.Options.Deadline is null)
            {
                context = new(context.Method, context.Host, context.Options.WithDeadline(DateTime.UtcNow.Add(deadline)));
            }
        }
    
        public override AsyncClientStreamingCall<TRequest, TResponse> AsyncClientStreamingCall<TRequest, TResponse>(ClientInterceptorContext<TRequest, TResponse> context, AsyncClientStreamingCallContinuation<TRequest, TResponse> continuation)
        {
            ApplyDeadline(ref context);
            return base.AsyncClientStreamingCall(context, continuation);
        }
    
        public override AsyncDuplexStreamingCall<TRequest, TResponse> AsyncDuplexStreamingCall<TRequest, TResponse>(ClientInterceptorContext<TRequest, TResponse> context, AsyncDuplexStreamingCallContinuation<TRequest, TResponse> continuation)
        {
            ApplyDeadline(ref context);
            return base.AsyncDuplexStreamingCall(context, continuation);
        }
    
        public override AsyncServerStreamingCall<TResponse> AsyncServerStreamingCall<TRequest, TResponse>(TRequest request, ClientInterceptorContext<TRequest, TResponse> context, AsyncServerStreamingCallContinuation<TRequest, TResponse> continuation)
        {
            ApplyDeadline(ref context);
            return base.AsyncServerStreamingCall(request, context, continuation);
        }
    
        public override AsyncUnaryCall<TResponse> AsyncUnaryCall<TRequest, TResponse>(TRequest request, ClientInterceptorContext<TRequest, TResponse> context, AsyncUnaryCallContinuation<TRequest, TResponse> continuation)
        {
            ApplyDeadline(ref context);
            return base.AsyncUnaryCall(request, context, continuation);
        }
    
        public override TResponse BlockingUnaryCall<TRequest, TResponse>(TRequest request, ClientInterceptorContext<TRequest, TResponse> context, BlockingUnaryCallContinuation<TRequest, TResponse> continuation)
        {
            ApplyDeadline(ref context);
            return base.BlockingUnaryCall(request, context, continuation);
        }
    
        // note no need to intercept server methods
    }