{"id":667,"date":"2020-09-30T06:07:55","date_gmt":"2020-09-30T13:07:55","guid":{"rendered":"https:\/\/devblogs.microsoft.com\/azure-sdk\/?p=667"},"modified":"2020-12-17T10:23:10","modified_gmt":"2020-12-17T18:23:10","slug":"net-framework-connection-pool-limits","status":"publish","type":"post","link":"https:\/\/devblogs.microsoft.com\/azure-sdk\/net-framework-connection-pool-limits\/","title":{"rendered":".NET Framework Connection Pool Limits and the new Azure SDK for .NET"},"content":{"rendered":"<p>To build scalable applications it&#8217;s important to understand how your downstream dependencies scale and what limitations you can hit.<\/p>\n<p>The majority of Azure services expose functionality over HTTP REST APIs. The Azure SDKs, in turn, wrap the HTTP communication into an easy-to-use set of client and model types.<\/p>\n<p>Every time you call a method on a <code>Client<\/code> class, an HTTP request is sent to the service. Sending an HTTP request requires a socket connection to be established between client and the server. Establishing a connection is an expensive operation that could take longer than the processing of the request itself. To combat this, .NET maintains a pool of HTTP connections that can be reused instead of opening a new one for each request.<\/p>\n<p>The post details the specifics of HTTP connection pooling based on the .NET runtime you are using and ways to tune it to make sure connection limits don&#8217;t negatively affect your application performance.<\/p>\n<p><strong>NOTE:<\/strong> most of this is not applicable for applications using .NET Core. See <a href=\"#.NET-Core\">.NET Core section<\/a> for details.<\/p>\n<h2>.NET Framework<\/h2>\n<p>Connection pooling in the .NET framework is controlled by the <a href=\"https:\/\/docs.microsoft.com\/dotnet\/api\/system.net.servicepointmanager\"><code>ServicePointManager<\/code><\/a> class and the most important fact to remember is that the pool, <a href=\"https:\/\/docs.microsoft.com\/dotnet\/api\/system.net.servicepointmanager.defaultconnectionlimit\">by default<\/a>, is limited to <strong>2<\/strong> connections to a particular endpoint (host+port pair) in non-web applications, and to <strong>unlimited<\/strong> connection per endpoint in ASP.NET applications that have <a href=\"https:\/\/docs.microsoft.com\/dotnet\/api\/system.web.configuration.processmodelsection.autoconfig\"><code>autoConfig<\/code><\/a> enabled (without <code>autoConfig<\/code> the limit is set to <strong>10<\/strong>). After the maximum number of connections is reached, HTTP requests will be queued until one of the existing connections becomes available again.<\/p>\n<p>Imagine writing a console application that uploads files to Azure Blob Storage. To speed up the process you decided to upload using <a href=\"https:\/\/docs.microsoft.com\/dotnet\/api\/azure.storage.storagetransferoptions.maximumconcurrency?view=azure-dotnet\">using 20 parallel threads<\/a>. The default connection pool limit means that even though you have 20 <a href=\"https:\/\/docs.microsoft.com\/dotnet\/api\/azure.storage.blobs.specialized.blockblobclient.uploadasync\"><code>BlockBlobClient.UploadAsync<\/code><\/a> calls running in parallel only 2 of them would be actually uploading data and the rest would be stuck in the queue.<\/p>\n<p><strong>NOTE:<\/strong> The connection pool is centrally managed on .NET Framework. Every <code>ServiceEndpoint<\/code> has one or more connection groups and the limit is applied to connections in a connection group. <code>HttpClient<\/code> creates a connection group per-client so every <code>HttpClient<\/code> instance gets it&#8217;s own limit while instances of <code>HttpWebRequest<\/code> reuse the default connection group and all share the same limit (unless <a href=\"https:\/\/docs.microsoft.com\/dotnet\/api\/system.net.httpwebrequest.connectiongroupname\">ConnectionGroupName<\/a> is set). All Azure SDK client by default use a shared instance of <code>HttpClient<\/code> and as such share the same pool of connections across all of them.<\/p>\n<h2>Symptoms of connection pool starvation<\/h2>\n<p>There are 3 main symptoms of connection pool starvation:<\/p>\n<ol>\n<li>Timeouts in the form of <a href=\"https:\/\/docs.microsoft.com\/dotnet\/api\/system.threading.tasks.taskcanceledexception\"><code>TaskCanceledException<\/code><\/a><\/li>\n<li>Latency spikes under load<\/li>\n<li>Low throughput<\/li>\n<\/ol>\n<p>Every outgoing HTTP request has a timeout associated with it (<a href=\"https:\/\/docs.microsoft.com\/dotnet\/api\/system.net.http.httpclient.timeout?view=netcore-3.1#remarks\">typically 100 seconds<\/a>) and the time waiting for a connection is counted towards the timeout. If no connection becomes available after the 100 seconds elapse the SDK call would fail with a <a href=\"https:\/\/docs.microsoft.com\/dotnet\/api\/system.threading.tasks.taskcanceledexception\"><code>TaskCanceledException<\/code><\/a>.<\/p>\n<p><strong>NOTE:<\/strong> because most Azure SDKs are set up to retry intermittent connection issues they would try sending the request multiple times before surfacing the failure, so it might take a multiple of default timeout to see the exception raised.<\/p>\n<p>A typical exception caused by connection pool starvation might look like the following:<\/p>\n<pre><code>System.AggregateException : Retry failed after 4 tries. (A task was canceled.) (A task was canceled.) (A task was canceled.) (A task was canceled.)\n  ----&gt; System.Threading.Tasks.TaskCanceledException : A task was canceled.\n  ----&gt; System.Threading.Tasks.TaskCanceledException : A task was canceled.\n  ----&gt; System.Threading.Tasks.TaskCanceledException : A task was canceled.\n  ----&gt; System.Threading.Tasks.TaskCanceledException : A task was canceled.\n   at Azure.Core.Pipeline.RetryPolicy.ProcessAsync(HttpMessage message, ReadOnlyMemory`1 pipeline, Boolean async)\n   at Azure.Core.Pipeline.HttpPipelineSynchronousPolicy.ProcessAsync(HttpMessage message, ReadOnlyMemory`1 pipeline)\n   at Azure.Core.Pipeline.HttpPipelineSynchronousPolicy.ProcessAsync(HttpMessage message, ReadOnlyMemory`1 pipeline)\n   at Azure.Core.Pipeline.HttpPipelineSynchronousPolicy.ProcessAsync(HttpMessage message, ReadOnlyMemory`1 pipeline)\n   at Azure.Core.Tests.PipelineTestBase.ExecuteRequest(HttpMessage message, HttpPipeline pipeline, CancellationToken cancellationToken)\n<\/code><\/pre>\n<p>Long-running requests with larger payloads or on slow network connection are more susceptible to timeout exceptions because they typically occupy connections for a longer time.<\/p>\n<p>Another less obvious symptom of a thread pool starvation is latency spikes. Let&#8217;s take a web application that typically serves around 10 customers at the same time. Because most of the time the connection requirement is under or just near the limit it&#8217;s operating with optimal performance. But the client count raising might causes it to hit the connection pool limit and makes parallel request compete for a limited connection pool resources increasing the response latency.<\/p>\n<p>Low throughput in parallelized workloads might be another symptom. Let&#8217;s take the console application we&#8217;ve discussed in the previous part. Considering that the local disk and network connection is fast and a single upload doesn&#8217;t saturate the entire network connection, adding more parallel uploads should increase network utilization and improve the overall throughput. But if application is limited by the connection pool size this won&#8217;t happen.<\/p>\n<h2>Avoid undisposed response streams<\/h2>\n<p>Another common way to starve the connection pool is by not disposing unbuffered streams returned by some client SDK methods.<\/p>\n<p>Most Azure SDK client methods will buffer and deserialize the response for you. But some methods operate on large blocks of data &#8211; that are impractical to fully load in memory &#8211; and would return an active network stream allowing data to be read and processed in chunks.<\/p>\n<p>These methods will have the stream as part of the <a href=\"https:\/\/docs.microsoft.com\/dotnet\/api\/azure.response-1.value\"><code>Value<\/code><\/a> inside the <a href=\"https:\/\/docs.microsoft.com\/dotnet\/api\/azure.response-1\"><code>Response&lt;T&gt;<\/code><\/a>. One common example of such a method is the <a href=\"https:\/\/docs.microsoft.com\/dotnet\/api\/azure.storage.blobs.specialized.blobbaseclient.downloadasync\"><code>BlockBlobClient.DownloadAsync<\/code><\/a> that returns <code>Response&lt;DownloadInfo&gt;<\/code> and <a href=\"https:\/\/docs.microsoft.com\/dotnet\/api\/azure.storage.blobs.models.blobdownloadinfo\"><code>BlobDownloadInfo<\/code><\/a> having a <a href=\"https:\/\/docs.microsoft.com\/dotnet\/api\/azure.storage.blobs.models.blobdownloadinfo.content\"><code>Content<\/code><\/a> property.<\/p>\n<p>Each of these streams represents a network connection borrowed from the pool and they are only returned when disposed or read to the end. By not doing that you are &#8220;burning&#8221; connections forever decreasing the pool size. This can quickly lead to a situation where there are no more connections to use for sending requests and all of the requests fail with a timeout exception.<\/p>\n<h2>Changing the limits<\/h2>\n<p>You can use <code>app.config<\/code>\/<code>web.config<\/code> files to change the limit or do it in code. It&#8217;s also possible to change the limit on per-endpoint basis.<\/p>\n<pre><code class=\"csharp\">ServicePoint servicePoint = ServicePointManager.FindServicePoint(new Uri(\"http:\/\/my-example-account.blob.core.windows.net\/\"));\nservicePoint.ConnectionLimit = 40;\n\nServicePointManager.DefaultConnectionLimit = 20;\n<\/code><\/pre>\n<pre><code class=\"xml\">&lt;configuration&gt;\n  &lt;system.net&gt;\n    &lt;connectionManagement&gt;\n      &lt;add address=\"http:\/\/my-example-account.blob.core.windows.com\" maxconnection=\"40\" \/&gt;\n      &lt;add address=\"*\" maxconnection=\"20\" \/&gt;\n    &lt;\/connectionManagement&gt;\n  &lt;\/system.net&gt;\n&lt;\/configuration&gt;\n<\/code><\/pre>\n<p>We recommend setting the limit to a maximum number of parallel request you expect to send and load testing\/monitoring your application to achieve the optimal performance.<\/p>\n<p><strong>NOTE:<\/strong> Default limits are applied when the first request is issued to a particular endpoint. After that changing the global value won&#8217;t have any effect on existing connections.<\/p>\n<p>The limit is also be changed by manually providing a customized <code>HttpClient<\/code> instance while constructing an Azure SDK client:<\/p>\n<pre><code class=\"csharp\">using HttpClient client = new HttpClient(\n    new HttpClientHandler()\n    {\n        MaxConnectionsPerServer = 40\n    });\n\nSecretClientOptions options = new SecretClientOptions\n{\n    Transport = new HttpClientTransport(client)\n};\n\nSecretClient client = new SecretClient(new Uri(\"&lt;endpoint&gt;\"), options);\n<\/code><\/pre>\n<h2>.NET Core<\/h2>\n<p>There was a major change around connection pool management in .NET Core. Connection pooling happens at the <code>HttpClient<\/code> level and the pool size is not limited by default. This means that HTTP connections would be automatically scaled to satisfy your workload and you shouldn&#8217;t be affected by issues described in this post.<\/p>\n<h2>Issues with an infinite connection pool size<\/h2>\n<p>Setting connection pool size to infinite might sound like a good idea but it has it&#8217;s own set of issues. Azure limits the amount of network connections a Virtual Machine or AppService instance can make and exceeding the limit would cause connections to be slowed down or terminated. If your application produces spikes of outbound requests an adjustment using <code>ServicePointManager<\/code> on .NET Framework or <a href=\"https:\/\/docs.microsoft.com\/dotnet\/api\/system.net.http.httpclienthandler.maxconnectionsperserver\">MaxConnectionsPerServer property<\/a> on .NET Core\/.NET Framework might be required to avoid exceeding the limit.<\/p>\n<p>You can read more about <a href=\"https:\/\/docs.microsoft.com\/en-us\/azure\/app-service\/troubleshoot-intermittent-outbound-connection-errors\">troubleshooting intermittent outbound connection errors in Azure App Service<\/a> and <a href=\"https:\/\/docs.microsoft.com\/en-us\/azure\/load-balancer\/load-balancer-outbound-connections\">load balancer limits<\/a>.<\/p>\n<h2>Future improvements Azure.Core<\/h2>\n<p>We are making changes in the upcoming Azure.Core library (<a href=\"https:\/\/github.com\/Azure\/azure-sdk-for-net\/pull\/15263\">#15263<\/a>) to automatically increase the connection pool size for Azure endpoints to <code>50<\/code> in applications where the global setting is kept at the default value of <code>2<\/code>. It will be fixed in the Azure.Core 1.5.1 October release.<\/p>\n<p><div  class=\"d-flex justify-content-center\"><a class=\"cta_button_link btn-primary mb-24\" href=\"https:\/\/aka.ms\/azsdk\/releases\" target=\"_blank\">Azure SDK Releases<\/a><\/div><\/p>\n<h2>Azure SDK Blog Contributions<\/h2>\n<p>Thank you for reading this Azure SDK blog post! We hope that you learned something new and welcome you to share this post. We are open to Azure SDK blog contributions. Please contact us at <a href=\"&#109;&#x61;&#105;&#x6c;&#116;&#x6f;&#58;&#x61;z&#115;&#x64;&#107;&#x62;&#108;&#x6f;&#103;&#x40;&#109;&#105;&#x63;&#114;&#x6f;&#115;&#x6f;&#102;&#x74;&#46;&#x63;o&#109;\">&#x61;z&#115;&#x64;&#107;&#x62;&#108;&#x6f;&#103;&#x40;&#109;&#105;&#x63;&#114;&#x6f;&#115;&#x6f;&#102;&#x74;&#46;&#x63;o&#109;<\/a> with your topic and we&#8217;ll get you setup as a guest blogger.<\/p>\n<h2>Azure SDK Links<\/h2>\n<ul>\n<li>Azure SDK Website: <a href=\"https:\/\/aka.ms\/azsdk\">aka.ms\/azsdk<\/a><\/li>\n<li>Azure SDK Intro (3 minute video): <a href=\"https:\/\/aka.ms\/azsdk\/intro\">aka.ms\/azsdk\/intro<\/a><\/li>\n<li>Azure SDK Intro Deck (PowerPoint deck): <a href=\"https:\/\/aka.ms\/azsdk\/intro\/deck\">aka.ms\/azsdk\/intro\/deck<\/a><\/li>\n<li>Azure SDK Releases: <a href=\"https:\/\/aka.ms\/azsdk\/releases\">aka.ms\/azsdk\/releases<\/a><\/li>\n<li>Azure SDK Blog: <a href=\"https:\/\/aka.ms\/azsdk\/blog\">aka.ms\/azsdk\/blog<\/a><\/li>\n<li>Azure SDK Twitter: <a href=\"https:\/\/twitter.com\/AzureSDK\">twitter.com\/AzureSDK<\/a><\/li>\n<li>Azure SDK Design Guidelines: <a href=\"https:\/\/aka.ms\/azsdk\/guide\">aka.ms\/azsdk\/guide<\/a><\/li>\n<li>Azure SDKs &amp; Tools: <a href=\"https:\/\/azure.microsoft.com\/downloads\">azure.microsoft.com\/downloads<\/a><\/li>\n<li>Azure SDK Central Repository: <a href=\"https:\/\/github.com\/azure\/azure-sdk#azure-sdk\">github.com\/azure\/azure-sdk<\/a><\/li>\n<li>Azure SDK for .NET: <a href=\"https:\/\/github.com\/azure\/azure-sdk-for-net\">github.com\/azure\/azure-sdk-for-net<\/a><\/li>\n<li>Azure SDK for Java: <a href=\"https:\/\/github.com\/azure\/azure-sdk-for-java\">github.com\/azure\/azure-sdk-for-java<\/a><\/li>\n<li>Azure SDK for Python: <a href=\"https:\/\/github.com\/azure\/azure-sdk-for-python\">github.com\/azure\/azure-sdk-for-python<\/a><\/li>\n<li>Azure SDK for JavaScript\/TypeScript: <a href=\"https:\/\/github.com\/azure\/azure-sdk-for-js\">github.com\/azure\/azure-sdk-for-js<\/a><\/li>\n<li>Azure SDK for Android: <a href=\"https:\/\/github.com\/Azure\/azure-sdk-for-android\">github.com\/Azure\/azure-sdk-for-android<\/a><\/li>\n<li>Azure SDK for iOS: <a href=\"https:\/\/github.com\/Azure\/azure-sdk-for-ios\">github.com\/Azure\/azure-sdk-for-ios<\/a><\/li>\n<li>Azure SDK for Go: <a href=\"https:\/\/github.com\/Azure\/azure-sdk-for-go\">github.com\/Azure\/azure-sdk-for-go<\/a><\/li>\n<li>Azure SDK for C: <a href=\"https:\/\/github.com\/Azure\/azure-sdk-for-c\">github.com\/Azure\/azure-sdk-for-c<\/a><\/li>\n<li>Azure SDK for C++: <a href=\"https:\/\/github.com\/Azure\/azure-sdk-for-cpp\">github.com\/Azure\/azure-sdk-for-cpp<\/a><\/li>\n<\/ul>\n<p><!-- FOOTER: DO NOT EDIT OR REMOVE --><\/p>\n","protected":false},"excerpt":{"rendered":"<p>Learn about .NET Framework connection pooling and how to take limits into account when writing applications that use the new Azure SDKs<\/p>\n","protected":false},"author":31981,"featured_media":668,"comment_status":"open","ping_status":"closed","sticky":false,"template":"","format":"standard","meta":{"_acf_changed":false,"footnotes":""},"categories":[1],"tags":[701,706,703,758,759,705,760],"class_list":["post-667","post","type-post","status-publish","format-standard","has-post-thumbnail","hentry","category-azure-sdk","tag-net","tag-azuresdk","tag-clientlibraries","tag-connectionpool","tag-httpclient","tag-sdk","tag-servicepointmanager"],"acf":[],"blog_post_summary":"<p>Learn about .NET Framework connection pooling and how to take limits into account when writing applications that use the new Azure SDKs<\/p>\n","_links":{"self":[{"href":"https:\/\/devblogs.microsoft.com\/azure-sdk\/wp-json\/wp\/v2\/posts\/667","targetHints":{"allow":["GET"]}}],"collection":[{"href":"https:\/\/devblogs.microsoft.com\/azure-sdk\/wp-json\/wp\/v2\/posts"}],"about":[{"href":"https:\/\/devblogs.microsoft.com\/azure-sdk\/wp-json\/wp\/v2\/types\/post"}],"author":[{"embeddable":true,"href":"https:\/\/devblogs.microsoft.com\/azure-sdk\/wp-json\/wp\/v2\/users\/31981"}],"replies":[{"embeddable":true,"href":"https:\/\/devblogs.microsoft.com\/azure-sdk\/wp-json\/wp\/v2\/comments?post=667"}],"version-history":[{"count":0,"href":"https:\/\/devblogs.microsoft.com\/azure-sdk\/wp-json\/wp\/v2\/posts\/667\/revisions"}],"wp:featuredmedia":[{"embeddable":true,"href":"https:\/\/devblogs.microsoft.com\/azure-sdk\/wp-json\/wp\/v2\/media\/668"}],"wp:attachment":[{"href":"https:\/\/devblogs.microsoft.com\/azure-sdk\/wp-json\/wp\/v2\/media?parent=667"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/devblogs.microsoft.com\/azure-sdk\/wp-json\/wp\/v2\/categories?post=667"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/devblogs.microsoft.com\/azure-sdk\/wp-json\/wp\/v2\/tags?post=667"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}