Synchronous HttpClient Methods Do Not Use Retry Resiliency Strategy
Description
Using Microsoft.Extensions.Http.Resilience 8.6.0
When adding HttpClient to the services container this works, and produces a client that can be retrieved by name, and with the associated resilience pipeline:
services.AddHttpClient("client-name")
.AddResilienceHandler("client-name-pipeline", builder =>
{
builder.AddRetry(new HttpRetryStrategyOptions
{
MaxRetryAttempts = 8,
UseJitter = true,
ShouldRetryAfterHeader = true,
Delay = TimeSpan.FromMilliseconds(100),
MaxDelay = TimeSpan.FromSeconds(10),
BackoffType = DelayBackoffType.Exponential,
OnRetry = (msg) =>
{
Debug.WriteLine("");
Debug.WriteLine("Now: " + DateTime.Now.ToString("O"));
Debug.WriteLine("RetryDelay: " + msg.RetryDelay);
Debug.WriteLine("");
return ValueTask.CompletedTask;
}
});
});
However, when using a typed HttpClient the implementation of the interface gets an HttpClient without any resilience pipeline. e.g. This provides an HttpClient in the implementation class that performs 0 retries:
services.AddHttpClient<IConfigurationServiceClient, ConfigurationServiceClient>((sp, client) =>
{
configure(sp, client);
})
.AddResilienceHandler("some name", builder =>
{
builder.AddRetry(new HttpRetryStrategyOptions
{
MaxRetryAttempts = 6,
UseJitter = true,
ShouldRetryAfterHeader = true,
Delay = TimeSpan.FromMilliseconds(100),
MaxDelay = TimeSpan.FromSeconds(10),
BackoffType = DelayBackoffType.Exponential,
OnRetry = (msg) =>
{
Debug.WriteLine("");
Debug.WriteLine("Now: " + DateTime.Now.ToString("O"));
Debug.WriteLine("RetryDelay: " + msg.RetryDelay);
Debug.WriteLine("");
return ValueTask.CompletedTask;
}
});
});
Reproduction Steps
Use code similar to above to use a typed HttpClient. Use the HttpClient and observe that no retries are performed.
Expected behavior
Typed HttpClients would be configured with resilience pipeline.
Actual behavior
Typed HttpClients do not appear to be configured with resilience pipeline.
Regression?
No response
Known Workarounds
No response
Configuration
.net 8.0
Other information
Using Microsoft.Extensions.Http.Resilience 8.6.0
OK, so I think I have a repo for this. It doesn't relate to the client being typed, but seem to relate to the fact our ConfigurationServiceClient uses synchronous methods on HttpClient.
See repo below.
With this registration
services.AddHttpClient<IConfigurationClientRepo, ConfigurationClientRepo>(client =>
{
client.Timeout = TimeSpan.FromSeconds(59);
})
.AddResilienceHandler("IConfigurationClient", builder =>
{
builder.AddRetry(new HttpRetryStrategyOptions
{
MaxRetryAttempts = 8,
UseJitter = true,
ShouldRetryAfterHeader = true,
Delay = TimeSpan.FromMilliseconds(100),
MaxDelay = TimeSpan.FromSeconds(10),
BackoffType = DelayBackoffType.Exponential,
OnRetry = (msg) =>
{
Debug.WriteLine("Now: " + DateTime.Now.ToString("O"));
Debug.WriteLine("RetryDelay: " + msg.RetryDelay);
return ValueTask.CompletedTask;
}
});
});
This code will NOT invoke the retires when GetRequest() is called, but will invoke the retries when GetRequestAsync() is called:
public class ConfigurationClientRepo(HttpClient httpClient) : IConfigurationClientRepo
{
public HttpResponseMessage GetRequest()
{
//This 408 failure does *NOT* invoke the resilience retry.
using HttpRequestMessage httpRequest = new(HttpMethod.Get, "https://httpstat.us/408");
using HttpResponseMessage httpResponse = httpClient.Send(httpRequest);
return httpResponse;
}
public async Task<HttpResponseMessage> GetRequestAsync()
{
//This 408 failure DOES invoke the resilience retry.
using HttpRequestMessage httpRequest = new(HttpMethod.Get, "https://httpstat.us/408");
using HttpResponseMessage httpResponse = await httpClient.SendAsync(httpRequest);
return httpResponse;
}
}
e.g.
//No retires performed:
_configurationClient.GetRequest();
//Retries performed:
await _configurationClient.GetRequestAsync();
Thanks for the report @phil000! @iliar-turdushev could you please take a look here?
This small console app repo's the issue using both a typed client and named client.
I am having the same issue. Is there any fix for this?
@phil000 Thank you for reporting this. We'll add support of resilience strategies for synchronous HttpClient requests. cc @YumeiTenerife
The change is available on https://dev.azure.com/dnceng/public/_artifacts/feed/dotnet8 feed. It will be formally released later this month.