Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Catching the last exception in Polly in .Net Core?

I'm using Polly (Microsoft.Extensions.Http.Polly) with .net core with this configuration (with an invalid URL , for testing) :

private static void RegisterServices()
{
    var collection = new ServiceCollection();
    var timeoutPolicy = Policy.TimeoutAsync<HttpResponseMessage>(2); // Timeout for an individual try
 

    collection.AddHttpClient<INetworkService, NetworkService>(url=>
             {
                 url.BaseAddress = new Uri("http://www.google.com:81"); //test bad url
             })
             .AddPolicyHandler(GetRetryPolicy()) 
             .AddPolicyHandler(timeoutPolicy); ;

    _serviceProvider = collection.BuildServiceProvider();
}

Where GetRetryPolicy is :

private static IAsyncPolicy<HttpResponseMessage> GetRetryPolicy()
{
    return HttpPolicyExtensions
        .HandleTransientHttpError()
        .OrResult(msg => msg.StatusCode != HttpStatusCode.OK)
        .Or<TimeoutRejectedException>()
        .Or<TaskCanceledException>()
        .Or<OperationCanceledException>()
        .WaitAndRetryAsync(3, retryAttempt =>
        {
        return  TimeSpan.FromSeconds(2);
        }, 
        onRetry: (response, delay, retryCount, context) =>
            {
              Console.WriteLine($"______PollyAttempt_____ retryCount:{retryCount}  ");
            });
}

Output is :

_PollyAttempt retryCount:1
_PollyAttempt retryCount:2
_PollyAttempt retryCount:3
Exception : (TimeoutException) The delegate executed asynchronously through TimeoutPolicy did not complete within the timeout.

I want to send an email after the last attempt was failing.

Question:

How can I catch that final exception? Is there any built-in mechanism that allows me to know that Polly was failed ?

(my currently working code : https://pastebin.pl/view/a2566d51)

like image 357
Royi Namir Avatar asked Sep 20 '25 09:09

Royi Namir


1 Answers

Let's start with a simple setup where you don't use Polly at all:

private static void RegisterServices()
{
    var collection = new ServiceCollection();
    collection.AddHttpClient<INetworkService, NetworkService>(sonol =>
    {
        sonol.BaseAddress = new Uri("http://www.google.com:81");
    });

    _serviceProvider = collection.BuildServiceProvider();
}
  • After 100 seconds (the default timeout of HttpClient) it will fail with a TaskCanceledException. In other words HttpClient cancels the request because it did not receive any response.

Now let's bend the HttpClient setup a bit:

private static void RegisterServices()
{
    var collection = new ServiceCollection();
    collection.AddHttpClient<INetworkService, NetworkService>(sonol =>
    {
        sonol.Timeout = TimeSpan.FromSeconds(3); // << NEW CODE
        sonol.BaseAddress = new Uri("http://www.google.com:81");
    });

    _serviceProvider = collection.BuildServiceProvider();
}
  • After 3 seconds the HttpClient cancels the request and throws a TaskCanceledException

Now, comment out this timeout setup and let's wire up the timeout Policy:

private static void RegisterServices()
{
    var collection = new ServiceCollection();
    var timeoutPolicy = Policy.TimeoutAsync<HttpResponseMessage>(2);

    collection.AddHttpClient<INetworkService, NetworkService>(sonol =>
    {
        //sonol.Timeout = TimeSpan.FromSeconds(3);
        sonol.BaseAddress = new Uri("http://www.google.com:81");
    })
    .AddPolicyHandler(timeoutPolicy); // << NEW CODE

    _serviceProvider = collection.BuildServiceProvider();
}
  • After 2 seconds Polly's TimeoutPolicy cancels the request and throws a TimeoutRejectedException.
    • Its InnerException is the original TaskCanceledException.

And finally let's add retry policy:

private static void RegisterServices()
{
    var collection = new ServiceCollection();
    var timeoutPolicy = Policy.TimeoutAsync<HttpResponseMessage>(2);

    collection.AddHttpClient<INetworkService, NetworkService>(sonol =>
    {
        //sonol.Timeout = TimeSpan.FromSeconds(3);
        sonol.BaseAddress = new Uri("http://www.google.com:81");
    })
    .AddPolicyHandler(Policy.WrapAsync(GetRetryPolicy(), timeoutPolicy)); // << NEW CODE
    //.AddPolicyHandler(timeoutPolicy);

    _serviceProvider = collection.BuildServiceProvider();
}
  • After 14 seconds (3+1 2 seconds long requests & 3 2 seconds long penalties) the retry policy throws the original exception, which is the innerPolicy's TimeoutRejectedException.
    • That exception's Inner is the HttpClient's TaskCanceledException.

UPDATE: Capture the essence of the comments

Where is the point where I know that all attempts have failed?

When your Polly decorated HttpClient throws the TimeoutRejectedException, you can be sure that all attempts failed. So, you should wrap the GetAsync with try-catch inside the typed client.

Should I check the exception to see that it's timeoutException?

In case of malformed url it will throw different exception. So, if you catch TimeoutRejectedException that would mean either the downstream is not available or it is overloaded.

Do I need to catch first TimeoutRejectedException exception in order to recognize retires have failed?

From the consumer perspective there will be a single exception. The retry policy will throw it

  • when it runs out of the retry attempts
  • or when it is not configured to handle it.
    • All those exceptions that are not listed explicitly via Handle<> or Or<> calls they are treated as unhandled. This means without any retry the policy will throw that.

In other words, TimeoutRejectedException will be thrown if the client will not receive answer from the downstream system for a given time period. But it might also throw HttpRequestException if there is some network issue.

  • If the retry is configure to handle that then you can be sure that if it is thrown then all retry attempts failed.
  • If it is not configured then without any retry it will throw the HttpRequestException.
like image 142
Peter Csala Avatar answered Sep 22 '25 08:09

Peter Csala



Donate For Us

If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!