Skip to content

Commit

Permalink
Increase connection pool size limit automatically (#15263)
Browse files Browse the repository at this point in the history
  • Loading branch information
pakrym authored Sep 18, 2020
1 parent 5bbe795 commit 92d72c3
Show file tree
Hide file tree
Showing 5 changed files with 119 additions and 2 deletions.
3 changes: 3 additions & 0 deletions sdk/core/Azure.Core/CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,9 @@

## 1.6.0-beta.1 (Unreleased)

### Changed
- `ServicePointManager` Connection limit is automatically increased to `50` for Azure endpoints.


## 1.5.0 (2020-09-03)

Expand Down
4 changes: 4 additions & 0 deletions sdk/core/Azure.Core/src/Pipeline/HttpClientTransport.cs
Original file line number Diff line number Diff line change
Expand Up @@ -86,6 +86,10 @@ private static HttpClient CreateDefaultClient()
httpClientHandler.Proxy = webProxy;
}

#if NETFRAMEWORK
ServicePointHelpers.SetLimits(httpClientHandler);
#endif

return new HttpClient(httpClientHandler);
}

Expand Down
3 changes: 3 additions & 0 deletions sdk/core/Azure.Core/src/Pipeline/HttpWebRequestTransport.cs
Original file line number Diff line number Diff line change
Expand Up @@ -48,6 +48,9 @@ public override async ValueTask ProcessAsync(HttpMessage message)
private async ValueTask ProcessInternal(HttpMessage message, bool async)
{
var request = CreateRequest(message.Request);

ServicePointHelpers.SetLimits(request.ServicePoint);

using var registration = message.CancellationToken.Register(state => ((HttpWebRequest) state).Abort(), request);
try
{
Expand Down
32 changes: 32 additions & 0 deletions sdk/core/Azure.Core/src/Pipeline/ServicePointHelpers.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
// Copyright (c) Microsoft Corporation. All rights reserved.
// Licensed under the MIT License.

using System.Net;
using System.Net.Http;

namespace Azure.Core.Pipeline
{
internal static class ServicePointHelpers
{
private const int RuntimeDefaultConnectionLimit = 2;
private const int IncreasedConnectionLimit = 50;

public static void SetLimits(ServicePoint requestServicePoint)
{
// Only change when the default runtime limit is used
if (requestServicePoint.ConnectionLimit == RuntimeDefaultConnectionLimit)
{
requestServicePoint.ConnectionLimit = IncreasedConnectionLimit;
}
}

public static void SetLimits(HttpClientHandler requestServicePoint)
{
// Only change when the default runtime limit is used
if (requestServicePoint.MaxConnectionsPerServer == RuntimeDefaultConnectionLimit)
{
requestServicePoint.MaxConnectionsPerServer = IncreasedConnectionLimit;
}
}
}
}
79 changes: 77 additions & 2 deletions sdk/core/Azure.Core/tests/HttpPipelineFunctionalTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -8,12 +8,12 @@
using System.Threading;
using System.Threading.Tasks;
using Azure.Core.Pipeline;
using Azure.Core.TestFramework;
using Microsoft.AspNetCore.Http;
using NUnit.Framework;

namespace Azure.Core.Tests
{

[TestFixture(typeof(HttpClientTransport), true)]
[TestFixture(typeof(HttpClientTransport), false)]
#if NETFRAMEWORK
Expand Down Expand Up @@ -158,6 +158,82 @@ public async Task NonBufferedFailedResponsesAreDisposedOf()
Assert.Greater(reqNum, requestCount);
}

[Test]
public async Task Opens50ParallelConnections()
{
// Running 50 sync requests on the threadpool would cause starvation
// and the test would take 20 sec to finish otherwise
ThreadPool.SetMinThreads(100, 100);

HttpPipeline httpPipeline = HttpPipelineBuilder.Build(GetOptions());
int reqNum = 0;

TaskCompletionSource<object> requestsTcs = new TaskCompletionSource<object>(TaskCreationOptions.RunContinuationsAsynchronously);

using TestServer testServer = new TestServer(
async context =>
{
if (Interlocked.Increment(ref reqNum) == 50)
{
requestsTcs.SetResult(true);
}
await requestsTcs.Task;
});

var requestCount = 50;
List<Task> requests = new List<Task>();
for (int i = 0; i < requestCount; i++)
{
HttpMessage message = httpPipeline.CreateMessage();
message.Request.Uri.Reset(testServer.Address);

requests.Add(Task.Run(() => ExecuteRequest(message, httpPipeline)));
}

await Task.WhenAll(requests);
}

[Test]
[Category("Live")]
public async Task Opens50ParallelConnectionsLive()
{
// Running 50 sync requests on the threadpool would cause starvation
// and the test would take 20 sec to finish otherwise
ThreadPool.SetMinThreads(100, 100);

HttpPipeline httpPipeline = HttpPipelineBuilder.Build(GetOptions());
int reqNum = 0;

TaskCompletionSource<object> requestsTcs = new TaskCompletionSource<object>(TaskCreationOptions.RunContinuationsAsynchronously);

async Task Connect()
{
using HttpMessage message = httpPipeline.CreateMessage();
message.Request.Uri.Reset(new Uri("https://www.microsoft.com/"));
message.BufferResponse = false;

await ExecuteRequest(message, httpPipeline);

if (Interlocked.Increment(ref reqNum) == 50)
{
requestsTcs.SetResult(true);
}

await requestsTcs.Task;
}

var requestCount = 50;
List<Task> requests = new List<Task>();
for (int i = 0; i < requestCount; i++)
{

requests.Add(Task.Run(() => Connect()));
}

await Task.WhenAll(requests);
}

[Test]
public async Task BufferedResponsesReadableAfterMessageDisposed()
{
Expand All @@ -176,7 +252,6 @@ public async Task BufferedResponsesReadableAfterMessageDisposed()
}
});

// Make sure we dispose things correctly and not exhaust the connection pool
var requestCount = 100;
for (int i = 0; i < requestCount; i++)
{
Expand Down

0 comments on commit 92d72c3

Please sign in to comment.