3

I have this implementation for subscribing the GRPC stream services, but, I have to identify when one of the services goes off and calls the event to notify the UI.

public async Task Subscribe()
{
     await Policy
          .Handle<RpcException>(e => e.Status.StatusCode == StatusCode.Unavailable)
          .WaitAndRetryAsync(
                    10,
                    attempt => TimeSpan.FromSeconds(Math.Pow(2, attempt)),
                    onRetry: (e, ts) => {
                        logger.Warning("Subscription connection lost. Trying to reconnect in {Seconds}s!", ts.Seconds);

                    })
          .ExecuteAsync(() => {
               IAsyncEnumerable<Notification> stream = await subscribe.Subscribe(currentUser)
               await foreach (Notification? ev in stream)
               {
                    switch (reply.ActionCase)
                    {
                        case Notification.ActionOneofCase.Service1:
                            logger.Warning("Incoming reply 'Service1'");
                            break;

                        case Notification.ActionOneofCase.Service2:
                            //TODO:
                            break;
                    }
            }
     });
}

I tried to use polly, but I don't know how to get when one specific service is down. I need to identify when one of the services is off to notify the UI. What would be the best approach to identity which service goes off?

EDIT:

That's how each service is injected:

private static void AddGrpcService<T>(IServiceCollection services,
                                        Config config) where T : class
{
    SocketsHttpHandler socketsHandler = new SocketsHttpHandler()
    {
        PooledConnectionIdleTimeout = Timeout.InfiniteTimeSpan,
        KeepAlivePingDelay = TimeSpan.FromSeconds(60),
        KeepAlivePingTimeout = TimeSpan.FromSeconds(30),
        EnableMultipleHttp2Connections = true
    };

    MethodConfig defaultMethodConfig = new MethodConfig
    {
        Names = { MethodName.Default },
        RetryPolicy = new RetryPolicy
        {
            MaxAttempts = 5,
            InitialBackoff = TimeSpan.FromSeconds(1),
            MaxBackoff = TimeSpan.FromSeconds(5),
            BackoffMultiplier = 1.5,
            RetryableStatusCodes = { StatusCode.Unavailable }
        }
    };

    ServiceConfig serviceConfig = new() { MethodConfigs = { defaultMethodConfig } };
    services.AddGrpcClient<T>(o => {
        o.Address = new Uri(config.GrpcUrl);
    })
            .ConfigureChannel(o => {
                o.Credentials = GetGrpcClientCredentials(config);
                o.ServiceConfig = serviceConfig;
            })
            .ConfigurePrimaryHttpMessageHandler(() => socketsHandler);
}
  • Could you please clarify where did you get stuck? Do you have problem to determine that a downstream service is down? Do you want to know where and how should you fire the notification? Or something else? – Peter Csala Aug 22 '23 at 17:58
  • It is exactly this, I would like to know how to catch which downstream service is down and fire the notification – Yago Oliveira Aug 22 '23 at 20:51

1 Answers1

1

Do you have problem to determine that a downstream service is down?

In case of HttpClient and Polly integration there is a static method, called HandleTransientHttpError. This triggers whenever the status code is 408 or 5xx. This also triggers in case of HttpRequestException.

Please bear in mind that it will NOT trigger for status code like 429 (Too Many Requests) which could also indicate that the downstream service is overloaded.

Initially I would have suggested to shoot for somewhat similar status codes. But since I'm not familiar with GRPC so, I'm just best guessing here based on documentation and this envoy issue.

readonly StatusCode[] RetriableStatusCodes = new[] 
{ 
  StatusCode.Cancelled, 
  StatusCode.DeadlineExceeded, 
  StatusCode.ResourceExhausted
};

...
await Policy
      .Handle<RpcException>(e => RetriableStatusCodes.Contains(e.Status.StatusCode))
      ...

Do you want to know where and how should you fire the notification?

The onRetry or the onRetryAsync could be the best place to fire a notification.

These user delegates are called if the policy will trigger and before the sleep.

In other words if the initial attempt fails and the Handle predicate is evaluated to true then it will call the onRetry(Async) delegate. After the delegate completed it will go to sleep before the first retry attempt.

The onRetry(Async) won't be called

  • if the Handle predicate is evaluated to false
  • if the max retry count is exceeded regardless the outcome of the Handle
Peter Csala
  • 17,736
  • 16
  • 35
  • 75
  • 1
    Many thanks, it help me a lot, how would be the best way to catch the state of the GRPC service client, is it possible to identify when Client channel is failing on Retry?? I'll edit the question add how each grpc service is injected – Yago Oliveira Aug 23 '23 at 16:12
  • @YagoOliveira *how would be the best way to catch the state of the GRPC service client* as I stated inside the response I'm not familiar with GRPC so, unfortunately I can't help with this. – Peter Csala Aug 24 '23 at 07:25
  • 1
    @YagoOliveira BTW please bear in mind that circuit breaker is the policy which is usually used to detect downstream inaccessibility. In case of Polly it counts consecutive failures or the proportion of success and failed requests (in case of [advanced circuit breaker](https://github.com/App-vNext/Polly/wiki/Advanced-Circuit-Breaker)). – Peter Csala Aug 24 '23 at 07:29
  • 1
    I'll accept this answer, the requirement for it has changed, so this answer I'll help me to finish it, thanks. – Yago Oliveira Aug 24 '23 at 19:13