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
.