Diagnosing Intermittent Connection Pool Exhaustion in Xamarin HttpClient
0 reputation · 02 Dec 2022, 14:16 UTC
0 reputation · 02 Dec 2022, 14:16 UTC
Xamarin’s HttpClient relies on platform‑specific handlers (OkHttp on Android, CFNetwork on iOS). The default connection pool is opaque; no public API exposes its size or timeout. When many concurrent requests are issued, the pool may silently drop connections after a timeout, leading to intermittent failures.
For projects targeting .NET Standard 2.0 or earlier, HttpClientFactory is unavailable, leaving developers to choose between creating a new HttpClient per request or reusing a single shared instance. The documentation does not specify the exact pool size, timeout thresholds, or how the underlying platform limits (e.g., OkHttp’s 5 max per host) interact with Xamarin’s pool, creating uncertainty around how to avoid exhaustion in high‑traffic scenarios.
What is the default connection pool size for Xamarin’s HttpClient on Android and iOS? How do platform limits affect the pool when many simultaneous requests are made? Does reusing a single HttpClient reliably prevent exhaustion, or are there hidden side effects such as stale DNS or cookie data?
Default pool size: Xamarin's HttpClient inherits platform limits — approximately 4–6 concurrent connections per host on iOS (NSUrlSession) and 5 per host on Android (OkHttp). These are hard OS caps; HttpClientHandler.MaxConnectionsPerServer is ignored on iOS and capped by OkHttp on Android.
Platform limit interaction: When concurrent requests exceed the per-host limit, the OS queues or drops connections silently. The symptom appears as intermittent TaskCanceledException or HttpRequestException with no clear timeout, because the pool cannot grow and requests stall until a slot frees.
Single shared HttpClient: Reusing one instance reliably prevents the dominant exhaustion cause (creating a new handler/pool per request). Side effects exist: stale DNS entries after TTL expiry and cookie/header persistence. Mitigate with PooledConnectionLifetime = TimeSpan.FromMinutes(2) on the handler to force periodic DNS re-resolution.
| Likely Explanation | Confirmed Fact |
|---|---|
Intermittent failures under load stem from per-request new HttpClient() leaking handlers until GC finalizes them. |
Each HttpClient constructor creates a new HttpClientHandler (and thus a new native pool). Disposing the client does not immediately release the native sockets. |
| Stale DNS after load-balancer IP rotation looks like pool exhaustion because retries consume remaining slots. | PooledConnectionLifetime (available on .NET Standard 2.0+ via HttpClientHandler) closes idle connections after the set window, triggering fresh DNS lookup. |
iOS enforces ~4–6 connections/host regardless of MaxConnectionsPerServer. |
Only a custom NSUrlSessionConfiguration injected via a derived handler can change this; not possible with stock HttpClientHandler. |
new HttpClient() inside request paths. Replace with a single static readonly HttpClient initialized at startup:
static readonly HttpClient Client = new HttpClient(new HttpClientHandler
{
PooledConnectionLifetime = TimeSpan.FromMinutes(2),
// MaxConnectionsPerServer is ignored on iOS; leave default on Android
});
dotnet-trace with System.Net.Http provider) and log every 30 s. The count must not grow with request rate.adb shell cat /proc/$(pidof your.package)/net/tcp — connections to the target host should plateau at ~5.lsof -p <pid> -i — plateau at ~4–6./etc/hosts (or use a rotating DNS record) to change the target IP while the app runs. Requests should recover within the PooledConnectionLifetime window without restart.Are you targeting .NET Standard 2.0 / Xamarin.Forms or have you migrated to .NET 6+ / MAUI? The answer changes whether PooledConnectionLifetime is available on the stock handler and whether HttpClientFactory (with built-in pooling) can be adopted instead of a manual singleton.
Use comments to ask for clarification. Post a solution as an answer.
32,775 reputation · 02 Dec 2022, 22:21 UTC
On iOS, the httpMaximumConnectionsPerHost = 6 limit applies to HTTP/1.1 connections; when the server negotiates HTTP/2, NSURLSession multiplexes multiple streams over a single connection, so the effective concurrent request count can exceed six without creating additional sockets.
On Android, OkHttp’s connection pool defaults to a maximum of five idle connections total (not just per host) and a keep‑alive of five minutes. The Xamarin AndroidClientHandler ignores HttpClientHandler.MaxConnectionsPerServer; to change the pool size you must supply a custom OkHttpClient via a handler constructor.