#519 Expose a connection limit option

This commit is contained in:
Chris R 2017-06-05 02:01:54 -07:00
parent 7c19149d2a
commit f1901516c6
5 changed files with 181 additions and 4 deletions

View File

@ -146,9 +146,7 @@ namespace Microsoft.AspNetCore.Server.HttpSys
return;
}
Options.Authentication.SetUrlGroupSecurity(UrlGroup);
Options.Timeouts.SetUrlGroupTimeouts(UrlGroup);
Options.SetRequestQueueLimit(RequestQueue);
Options.Apply(UrlGroup, RequestQueue);
_requestQueue.AttachToUrlGroup();

View File

@ -12,7 +12,9 @@ namespace Microsoft.AspNetCore.Server.HttpSys
// The native request queue
private long _requestQueueLength = DefaultRequestQueueLength;
private long? _maxConnections;
private RequestQueue _requestQueue;
private UrlGroup _urlGroup;
public HttpSysOptions()
{
@ -54,6 +56,29 @@ namespace Microsoft.AspNetCore.Server.HttpSys
/// </summary>
public bool ThrowWriteExceptions { get; set; }
/// <summary>
/// Gets or sets the maximum number of concurrent connections to accept, -1 for infinite, or null to
/// use the machine wide setting from the registry. The default value is null.
/// </summary>
public long? MaxConnections
{
get => _maxConnections;
set
{
if (value.HasValue && value < -1)
{
throw new ArgumentOutOfRangeException(nameof(value), value, string.Empty);
}
if (value.HasValue && _urlGroup != null)
{
_urlGroup.SetMaxConnections(value.Value);
}
_maxConnections = value;
}
}
/// <summary>
/// Gets or sets the maximum number of requests that will be queued up in Http.Sys.
/// </summary>
@ -79,13 +104,23 @@ namespace Microsoft.AspNetCore.Server.HttpSys
}
}
internal void SetRequestQueueLimit(RequestQueue requestQueue)
internal void Apply(UrlGroup urlGroup, RequestQueue requestQueue)
{
_urlGroup = urlGroup;
_requestQueue = requestQueue;
if (_maxConnections.HasValue)
{
_urlGroup.SetMaxConnections(_maxConnections.Value);
}
if (_requestQueueLength != DefaultRequestQueueLength)
{
_requestQueue.SetLengthLimit(_requestQueueLength);
}
Authentication.SetUrlGroupSecurity(urlGroup);
Timeouts.SetUrlGroupTimeouts(urlGroup);
}
}
}

View File

@ -544,6 +544,13 @@ namespace Microsoft.AspNetCore.Server.HttpSys
HttpRequestAuthTypeKerberos
}
internal enum HTTP_QOS_SETTING_TYPE
{
HttpQosSettingTypeBandwidth,
HttpQosSettingTypeConnectionLimit,
HttpQosSettingTypeFlowRate
}
[StructLayout(LayoutKind.Sequential)]
internal struct HTTP_SERVER_AUTHENTICATION_INFO
{
@ -604,6 +611,20 @@ namespace Microsoft.AspNetCore.Server.HttpSys
internal IntPtr RequestQueueHandle;
}
[StructLayout(LayoutKind.Sequential)]
internal struct HTTP_CONNECTION_LIMIT_INFO
{
internal HTTP_FLAGS Flags;
internal uint MaxConnections;
}
[StructLayout(LayoutKind.Sequential)]
internal struct HTTP_QOS_SETTING_INFO
{
internal HTTP_QOS_SETTING_TYPE QosType;
internal IntPtr QosSetting;
}
// see http.w for definitions
[Flags]
internal enum HTTP_FLAGS : uint

View File

@ -3,12 +3,16 @@
using System;
using System.Diagnostics;
using System.Runtime.InteropServices;
using Microsoft.Extensions.Logging;
namespace Microsoft.AspNetCore.Server.HttpSys
{
internal class UrlGroup : IDisposable
{
private static readonly int QosInfoSize =
Marshal.SizeOf<HttpApi.HTTP_QOS_SETTING_INFO>();
private ServerSession _serverSession;
private ILogger _logger;
private bool _disposed;
@ -33,6 +37,19 @@ namespace Microsoft.AspNetCore.Server.HttpSys
internal ulong Id { get; private set; }
internal unsafe void SetMaxConnections(long maxConnections)
{
var connectionLimit = new HttpApi.HTTP_CONNECTION_LIMIT_INFO();
connectionLimit.Flags = HttpApi.HTTP_FLAGS.HTTP_PROPERTY_FLAG_PRESENT;
connectionLimit.MaxConnections = (uint)maxConnections;
var qosSettings = new HttpApi.HTTP_QOS_SETTING_INFO();
qosSettings.QosType = HttpApi.HTTP_QOS_SETTING_TYPE.HttpQosSettingTypeConnectionLimit;
qosSettings.QosSetting = new IntPtr(&connectionLimit);
SetProperty(HttpApi.HTTP_SERVER_PROPERTY.HttpServerQosProperty, new IntPtr(&qosSettings), (uint)QosInfoSize);
}
internal void SetProperty(HttpApi.HTTP_SERVER_PROPERTY property, IntPtr info, uint infosize, bool throwOnError = true)
{
Debug.Assert(info != IntPtr.Zero, "SetUrlGroupProperty called with invalid pointer");

View File

@ -300,6 +300,112 @@ namespace Microsoft.AspNetCore.Server.HttpSys
}
}
[ConditionalFact]
public void Server_SetConnectionLimitArgumentValidation_Success()
{
var server = Utilities.CreatePump();
Assert.Null(server.Listener.Options.MaxConnections);
Assert.Throws<ArgumentOutOfRangeException>(() => server.Listener.Options.MaxConnections = -2);
Assert.Null(server.Listener.Options.MaxConnections);
server.Listener.Options.MaxConnections = null;
server.Listener.Options.MaxConnections = 3;
}
[ConditionalFact]
public async Task Server_SetConnectionLimit_Success()
{
// This is just to get a dynamic port
string address;
using (Utilities.CreateHttpServer(out address, httpContext => Task.FromResult(0))) { }
var server = Utilities.CreatePump();
server.Listener.Options.UrlPrefixes.Add(UrlPrefix.Create(address));
Assert.Null(server.Listener.Options.MaxConnections);
server.Listener.Options.MaxConnections = 3;
using (server)
{
await server.StartAsync(new DummyApplication(), CancellationToken.None);
using (var client1 = await SendHungRequestAsync("GET", address))
using (var client2 = await SendHungRequestAsync("GET", address))
{
using (var client3 = await SendHungRequestAsync("GET", address))
{
// Maxed out, refuses connection and throws
await Assert.ThrowsAsync<HttpRequestException>(() => SendRequestAsync(address));
}
// A connection has been closed, try again.
string responseText = await SendRequestAsync(address);
Assert.Equal(string.Empty, responseText);
}
}
}
[ConditionalFact]
public async Task Server_SetConnectionLimitChangeAfterStarted_Success()
{
// This is just to get a dynamic port
string address;
using (Utilities.CreateHttpServer(out address, httpContext => Task.FromResult(0))) { }
var server = Utilities.CreatePump();
server.Listener.Options.UrlPrefixes.Add(UrlPrefix.Create(address));
Assert.Null(server.Listener.Options.MaxConnections);
server.Listener.Options.MaxConnections = 3;
using (server)
{
await server.StartAsync(new DummyApplication(), CancellationToken.None);
using (var client1 = await SendHungRequestAsync("GET", address))
using (var client2 = await SendHungRequestAsync("GET", address))
using (var client3 = await SendHungRequestAsync("GET", address))
{
// Maxed out, refuses connection and throws
await Assert.ThrowsAsync<HttpRequestException>(() => SendRequestAsync(address));
server.Listener.Options.MaxConnections = 4;
string responseText = await SendRequestAsync(address);
Assert.Equal(string.Empty, responseText);
server.Listener.Options.MaxConnections = 2;
// Maxed out, refuses connection and throws
await Assert.ThrowsAsync<HttpRequestException>(() => SendRequestAsync(address));
}
}
}
[ConditionalFact]
public async Task Server_SetConnectionLimitInfinite_Success()
{
// This is just to get a dynamic port
string address;
using (Utilities.CreateHttpServer(out address, httpContext => Task.FromResult(0))) { }
var server = Utilities.CreatePump();
server.Listener.Options.UrlPrefixes.Add(UrlPrefix.Create(address));
server.Listener.Options.MaxConnections = -1; // infinite
using (server)
{
await server.StartAsync(new DummyApplication(), CancellationToken.None);
using (var client1 = await SendHungRequestAsync("GET", address))
using (var client2 = await SendHungRequestAsync("GET", address))
using (var client3 = await SendHungRequestAsync("GET", address))
{
// Doesn't max out
string responseText = await SendRequestAsync(address);
Assert.Equal(string.Empty, responseText);
}
}
}
private async Task<string> SendRequestAsync(string uri)
{
using (HttpClient client = new HttpClient())