gotchacsharpdotnetMajor
IHttpClientFactory: avoiding socket exhaustion and DNS refresh issues
Viewed 0 times
IHttpClientFactory socket exhaustionHttpClient DNS refreshAddHttpClienttyped clienthandler lifetime
Error Messages
Problem
Creating a new HttpClient per request exhausts sockets (TIME_WAIT). Using a static HttpClient reuses sockets but ignores DNS changes, causing failures after load balancer updates.
Solution
Use IHttpClientFactory which pools handlers and rotates them on a timer:
For Polly policies on .NET 8+:
// Registration — typed client
public class PaymentsClient
{
private readonly HttpClient _http;
public PaymentsClient(HttpClient http) => _http = http;
public async Task<PaymentResult> ChargeAsync(PaymentRequest req)
=> await _http.PostAsJsonAsync("/charge", req)
.GetContent<PaymentResult>();
}
builder.Services.AddHttpClient<PaymentsClient>(client =>
{
client.BaseAddress = new Uri(builder.Configuration["Payments:BaseUrl"]!);
client.Timeout = TimeSpan.FromSeconds(30);
})
.AddStandardResilienceHandler(); // Polly retry/circuit breaker (.NET 8)
// Named client
builder.Services.AddHttpClient("github", client =>
client.BaseAddress = new Uri("https://api.github.com/"));For Polly policies on .NET 8+:
.AddStandardResilienceHandler(options =>
options.Retry.MaxRetryAttempts = 3);Why
IHttpClientFactory manages a pool of HttpMessageHandler instances. Each handler has a HandlerLifetime (default 2 minutes) — after which it is retired and a new one created for the next request. This allows DNS to refresh while preventing socket exhaustion.
Gotchas
- Do not dispose HttpClient obtained from IHttpClientFactory — the factory manages the handler lifetime, not the client lifetime
- Typed clients are registered as transient — create a new one per operation, not once in a constructor
- Custom DelegatingHandlers added via AddHttpMessageHandler are instantiated per request — make them stateless
Revisions (0)
No revisions yet.