The Error
System.Net.Sockets.SocketException: Only one usage of each socket address is normally permitted
Quick Fix - 2 Minutes
// BAD: new HttpClient() in loop causes socket exhaustion
// GOOD: Dev Fix using HttpClientFactory .NET 8
// Program.cs
builder.Services.AddHttpClient("GitHub", client =>
{
client.BaseAddress = new Uri("https://api.github.com/");
client.DefaultRequestHeaders.Add("User-Agent", "DevFix");
});
// Inject IHttpClientFactory
public class GitHubService
{
private readonly IHttpClientFactory _factory;
public GitHubService(IHttpClientFactory factory) => _factory = factory;
public async Task<string> Get()
{
var client = _factory.CreateClient("GitHub"); // Reuses handlers
return await client.GetStringAsync("/");
}
}
Why This Happens
new HttpClient() creates a new socket per instance. Under load, Windows runs out of ports in TIME_WAIT. HttpClientFactory pools handlers and reuses sockets. .NET 8 default handler lifetime is 2 minutes.
Real-World Scenario: Why Singleton HttpClient Still Breaks
Devs "fix" socket exhaustion with static HttpClient, then get DNS issues:
// WRONG: Singleton HttpClient caches DNS forever
public static class ApiClient
{
private static readonly HttpClient client = new HttpClient(); // BAD
}
// RIGHT: Typed Client with handler rotation
// Program.cs
builder.Services.AddHttpClient(client =>
{
client.BaseAddress = new Uri("https://api.stripe.com/");
client.Timeout = TimeSpan.FromSeconds(30);
})
.SetHandlerLifetime(TimeSpan.FromMinutes(5)); // Dev Fix: Rotate DNS every 5min
// PaymentService.cs
public class PaymentService
{
private readonly HttpClient _client;
public PaymentService(HttpClient client) => _client = client; // Injected
public async Task ChargeAsync() => await _client.PostAsync("/charges", null);
}
Why Singleton breaks: HttpClient caches DNS for the lifetime of the handler. If Stripe changes IPs, your static client keeps hitting the old dead IP. SetHandlerLifetime forces DNS refresh. Factory default is 2 minutes.
Related Fixes You Should Know
Socket exhaustion triggers these next:
- TaskCanceledException HttpClient Timeout - Socket exhaustion manifests as timeouts. OS kills new connections =
TaskCanceledException. Fix factory first, then timeout. - Polly Retry Not Working - You added Polly but still get socket errors.
AddPolicyHandlermust come afterAddHttpClient, not before. - 429 Too Many Requests - High throughput + no factory = socket exhaustion + rate limits. Factory + Polly retry + jitter solves both.
- HttpClient DelegatingHandler DI Scope - Injecting scoped logger into handler causes
ObjectDisposedException. UseIHttpMessageHandlerFactory.
FAQ
Q: How do I check if I have socket exhaustion?
Run netstat -ano | find "TIME_WAIT" on Windows. If you see thousands of entries to port 443, you're leaking sockets. Or check PerfView for System.Net.Sockets events.
Q: Is using static HttpClient ever OK?
No. Even Microsoft docs say don't. Static HttpClient never refreshes DNS. If your API moves servers, you'll get timeouts until app restart. Use AddHttpClient typed client instead.
Best Practice for .NET 8
- Never
new HttpClient()- useAddHttpClient()DI - For named clients:
_factory.CreateClient("Name") - For typed clients:
builder.Services.AddHttpClient() - Singleton HttpClient is wrong too - DNS changes won't refresh
- Set handler lifetime:
.SetHandlerLifetime(TimeSpan.FromMinutes(5))if needed
No comments yet. Be the first to share your thoughts!