Improve ConnectionLimitMiddleware and connection pipeline building (#2010)

* Improve ConnectionLimitMiddleware and connection pipeline building
* Add IDecrementConcurrentConnectionCountFeature
* Flow connection features from connection middleware
This commit is contained in:
Stephen Halter 2017-08-30 11:30:20 -07:00 committed by GitHub
parent e7743cbb78
commit 284367db9f
31 changed files with 156 additions and 94 deletions

View File

@ -85,6 +85,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Performance
var frameContext = new FrameContext var frameContext = new FrameContext
{ {
ServiceContext = serviceContext, ServiceContext = serviceContext,
ConnectionFeatures = new FeatureCollection(),
PipeFactory = new PipeFactory() PipeFactory = new PipeFactory()
}; };

View File

@ -3,6 +3,7 @@
using System.IO.Pipelines; using System.IO.Pipelines;
using BenchmarkDotNet.Attributes; using BenchmarkDotNet.Attributes;
using Microsoft.AspNetCore.Http.Features;
using Microsoft.AspNetCore.Server.Kestrel.Core; using Microsoft.AspNetCore.Server.Kestrel.Core;
using Microsoft.AspNetCore.Server.Kestrel.Core.Internal; using Microsoft.AspNetCore.Server.Kestrel.Core.Internal;
using Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http; using Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http;
@ -29,6 +30,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Performance
var frameContext = new FrameContext var frameContext = new FrameContext
{ {
ServiceContext = serviceContext, ServiceContext = serviceContext,
ConnectionFeatures = new FeatureCollection(),
PipeFactory = new PipeFactory(), PipeFactory = new PipeFactory(),
TimeoutControl = new MockTimeoutControl() TimeoutControl = new MockTimeoutControl()
}; };

View File

@ -7,6 +7,7 @@ using System.Text;
using System.Threading; using System.Threading;
using System.Threading.Tasks; using System.Threading.Tasks;
using BenchmarkDotNet.Attributes; using BenchmarkDotNet.Attributes;
using Microsoft.AspNetCore.Http.Features;
using Microsoft.AspNetCore.Server.Kestrel.Core; using Microsoft.AspNetCore.Server.Kestrel.Core;
using Microsoft.AspNetCore.Server.Kestrel.Core.Internal; using Microsoft.AspNetCore.Server.Kestrel.Core.Internal;
using Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http; using Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http;
@ -107,6 +108,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Performance
var frame = new TestFrame<object>(application: null, context: new FrameContext var frame = new TestFrame<object>(application: null, context: new FrameContext
{ {
ServiceContext = serviceContext, ServiceContext = serviceContext,
ConnectionFeatures = new FeatureCollection(),
PipeFactory = pipeFactory, PipeFactory = pipeFactory,
Application = pair.Application, Application = pair.Application,
Transport = pair.Transport Transport = pair.Transport

View File

@ -3,6 +3,7 @@
using System.IO.Pipelines; using System.IO.Pipelines;
using BenchmarkDotNet.Attributes; using BenchmarkDotNet.Attributes;
using Microsoft.AspNetCore.Http.Features;
using Microsoft.AspNetCore.Server.Kestrel.Core; using Microsoft.AspNetCore.Server.Kestrel.Core;
using Microsoft.AspNetCore.Server.Kestrel.Core.Internal; using Microsoft.AspNetCore.Server.Kestrel.Core.Internal;
using Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http; using Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http;
@ -33,6 +34,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Performance
var frameContext = new FrameContext var frameContext = new FrameContext
{ {
ServiceContext = serviceContext, ServiceContext = serviceContext,
ConnectionFeatures = new FeatureCollection(),
PipeFactory = PipeFactory, PipeFactory = PipeFactory,
TimeoutControl = new MockTimeoutControl() TimeoutControl = new MockTimeoutControl()
}; };

View File

@ -6,6 +6,7 @@ using System.Runtime.CompilerServices;
using System.Text; using System.Text;
using BenchmarkDotNet.Attributes; using BenchmarkDotNet.Attributes;
using Microsoft.AspNetCore.Http; using Microsoft.AspNetCore.Http;
using Microsoft.AspNetCore.Http.Features;
using Microsoft.AspNetCore.Http.Internal; using Microsoft.AspNetCore.Http.Internal;
using Microsoft.AspNetCore.Server.Kestrel.Core; using Microsoft.AspNetCore.Server.Kestrel.Core;
using Microsoft.AspNetCore.Server.Kestrel.Core.Internal; using Microsoft.AspNetCore.Server.Kestrel.Core.Internal;
@ -177,6 +178,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Performance
var frameContext = new FrameContext var frameContext = new FrameContext
{ {
ServiceContext = serviceContext, ServiceContext = serviceContext,
ConnectionFeatures = new FeatureCollection(),
PipeFactory = new PipeFactory() PipeFactory = new PipeFactory()
}; };

View File

@ -7,6 +7,7 @@ using System.Text;
using System.Threading; using System.Threading;
using System.Threading.Tasks; using System.Threading.Tasks;
using BenchmarkDotNet.Attributes; using BenchmarkDotNet.Attributes;
using Microsoft.AspNetCore.Http.Features;
using Microsoft.AspNetCore.Server.Kestrel.Core; using Microsoft.AspNetCore.Server.Kestrel.Core;
using Microsoft.AspNetCore.Server.Kestrel.Core.Internal; using Microsoft.AspNetCore.Server.Kestrel.Core.Internal;
using Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http; using Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http;
@ -123,6 +124,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Performance
var frame = new TestFrame<object>(application: null, context: new FrameContext var frame = new TestFrame<object>(application: null, context: new FrameContext
{ {
ServiceContext = serviceContext, ServiceContext = serviceContext,
ConnectionFeatures = new FeatureCollection(),
PipeFactory = pipeFactory, PipeFactory = pipeFactory,
TimeoutControl = new MockTimeoutControl(), TimeoutControl = new MockTimeoutControl(),
Application = pair.Application, Application = pair.Application,

View File

@ -0,0 +1,17 @@
// Copyright (c) .NET Foundation. All rights reserved.
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
namespace Microsoft.AspNetCore.Server.Kestrel.Core.Features
{
/// <summary>
/// A connection feature allowing middleware to stop counting connections towards <see cref="KestrelServerLimits.MaxConcurrentConnections"/>.
/// This is used by Kestrel internally to stop counting upgraded connections towards this limit.
/// </summary>
public interface IDecrementConcurrentConnectionCountFeature
{
/// <summary>
/// Idempotent method to stop counting a connection towards <see cref="KestrelServerLimits.MaxConcurrentConnections"/>.
/// </summary>
void ReleaseConnection();
}
}

View File

@ -1,16 +0,0 @@
using Microsoft.AspNetCore.Protocols;
namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal
{
public static class ConnectionLimitBuilderExtensions
{
public static IConnectionBuilder UseConnectionLimit(this IConnectionBuilder builder, ServiceContext serviceContext)
{
return builder.Use(next =>
{
var middleware = new ConnectionLimitMiddleware(next, serviceContext);
return middleware.OnConnectionAsync;
});
}
}
}

View File

@ -1,32 +1,74 @@
using System.Threading.Tasks; // Copyright (c) .NET Foundation. All rights reserved.
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
using System.Threading.Tasks;
using Microsoft.AspNetCore.Protocols; using Microsoft.AspNetCore.Protocols;
using Microsoft.AspNetCore.Server.Kestrel.Core.Features;
using Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Infrastructure; using Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Infrastructure;
namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal
{ {
public class ConnectionLimitMiddleware public class ConnectionLimitMiddleware
{ {
private readonly ServiceContext _serviceContext;
private readonly ConnectionDelegate _next; private readonly ConnectionDelegate _next;
private readonly ResourceCounter _concurrentConnectionCounter;
private readonly IKestrelTrace _trace;
public ConnectionLimitMiddleware(ConnectionDelegate next, ServiceContext serviceContext) public ConnectionLimitMiddleware(ConnectionDelegate next, long connectionLimit, IKestrelTrace trace)
: this(next, ResourceCounter.Quota(connectionLimit), trace)
{ {
_next = next;
_serviceContext = serviceContext;
} }
public Task OnConnectionAsync(ConnectionContext connection) // For Testing
internal ConnectionLimitMiddleware(ConnectionDelegate next, ResourceCounter concurrentConnectionCounter, IKestrelTrace trace)
{ {
if (!_serviceContext.ConnectionManager.NormalConnectionCount.TryLockOne()) _next = next;
_concurrentConnectionCounter = concurrentConnectionCounter;
_trace = trace;
}
public async Task OnConnectionAsync(ConnectionContext connection)
{
if (!_concurrentConnectionCounter.TryLockOne())
{ {
KestrelEventSource.Log.ConnectionRejected(connection.ConnectionId); KestrelEventSource.Log.ConnectionRejected(connection.ConnectionId);
_serviceContext.Log.ConnectionRejected(connection.ConnectionId); _trace.ConnectionRejected(connection.ConnectionId);
connection.Transport.Input.Complete(); connection.Transport.Input.Complete();
connection.Transport.Output.Complete(); connection.Transport.Output.Complete();
return Task.CompletedTask; return;
} }
return _next(connection); var releasor = new ConnectionReleasor(_concurrentConnectionCounter);
try
{
connection.Features.Set<IDecrementConcurrentConnectionCountFeature>(releasor);
await _next(connection);
}
finally
{
releasor.ReleaseConnection();
}
}
private class ConnectionReleasor : IDecrementConcurrentConnectionCountFeature
{
private readonly ResourceCounter _concurrentConnectionCounter;
private bool _connectionReleased;
public ConnectionReleasor(ResourceCounter normalConnectionCounter)
{
_concurrentConnectionCounter = normalConnectionCounter;
}
public void ReleaseConnection()
{
if (!_connectionReleased)
{
_connectionReleased = true;
_concurrentConnectionCounter.ReleaseOne();
}
}
} }
} }
} }

View File

@ -141,7 +141,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal
adaptedPipelineTask = adaptedPipeline.RunAsync(stream); adaptedPipelineTask = adaptedPipeline.RunAsync(stream);
} }
if (_frame.ConnectionFeatures?.Get<ITlsApplicationProtocolFeature>()?.ApplicationProtocol == "h2" && if (_frame.ConnectionFeatures.Get<ITlsApplicationProtocolFeature>()?.ApplicationProtocol == "h2" &&
Interlocked.CompareExchange(ref _http2ConnectionState, Http2ConnectionStarted, Http2ConnectionNotStarted) == Http2ConnectionNotStarted) Interlocked.CompareExchange(ref _http2ConnectionState, Http2ConnectionStarted, Http2ConnectionNotStarted) == Http2ConnectionNotStarted)
{ {
await _http2Connection.ProcessAsync(httpApplication); await _http2Connection.ProcessAsync(httpApplication);
@ -167,10 +167,6 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal
{ {
_context.ServiceContext.ConnectionManager.UpgradedConnectionCount.ReleaseOne(); _context.ServiceContext.ConnectionManager.UpgradedConnectionCount.ReleaseOne();
} }
else
{
_context.ServiceContext.ConnectionManager.NormalConnectionCount.ReleaseOne();
}
KestrelEventSource.Log.ConnectionStop(this); KestrelEventSource.Log.ConnectionStop(this);
} }
@ -181,6 +177,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal
_frame = new Frame<TContext>(httpApplication, new FrameContext _frame = new Frame<TContext>(httpApplication, new FrameContext
{ {
ConnectionId = _context.ConnectionId, ConnectionId = _context.ConnectionId,
ConnectionFeatures = _context.ConnectionFeatures,
PipeFactory = PipeFactory, PipeFactory = PipeFactory,
LocalEndPoint = LocalEndPoint, LocalEndPoint = LocalEndPoint,
RemoteEndPoint = RemoteEndPoint, RemoteEndPoint = RemoteEndPoint,
@ -255,10 +252,9 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal
{ {
Debug.Assert(_frame != null, $"{nameof(_frame)} is null"); Debug.Assert(_frame != null, $"{nameof(_frame)} is null");
var features = new FeatureCollection();
var connectionAdapters = _context.ConnectionAdapters; var connectionAdapters = _context.ConnectionAdapters;
var stream = new RawStream(_context.Transport.Input, _context.Transport.Output); var stream = new RawStream(_context.Transport.Input, _context.Transport.Output);
var adapterContext = new ConnectionAdapterContext(features, stream); var adapterContext = new ConnectionAdapterContext(_frame.ConnectionFeatures, stream);
_adaptedConnections = new List<IAdaptedConnection>(connectionAdapters.Count); _adaptedConnections = new List<IAdaptedConnection>(connectionAdapters.Count);
try try
@ -267,7 +263,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal
{ {
var adaptedConnection = await connectionAdapters[i].OnConnectionAsync(adapterContext); var adaptedConnection = await connectionAdapters[i].OnConnectionAsync(adapterContext);
_adaptedConnections.Add(adaptedConnection); _adaptedConnections.Add(adaptedConnection);
adapterContext = new ConnectionAdapterContext(features, adaptedConnection.ConnectionStream); adapterContext = new ConnectionAdapterContext(_frame.ConnectionFeatures, adaptedConnection.ConnectionStream);
} }
} }
catch (Exception ex) catch (Exception ex)
@ -276,10 +272,6 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal
return null; return null;
} }
finally
{
_frame.ConnectionFeatures = features;
}
return adapterContext.ConnectionStream; return adapterContext.ConnectionStream;
} }

View File

@ -4,6 +4,7 @@
using System.Collections.Generic; using System.Collections.Generic;
using System.IO.Pipelines; using System.IO.Pipelines;
using System.Net; using System.Net;
using Microsoft.AspNetCore.Http.Features;
using Microsoft.AspNetCore.Server.Kestrel.Core.Adapter.Internal; using Microsoft.AspNetCore.Server.Kestrel.Core.Adapter.Internal;
namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal
@ -13,6 +14,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal
public string ConnectionId { get; set; } public string ConnectionId { get; set; }
public long FrameConnectionId { get; set; } public long FrameConnectionId { get; set; }
public ServiceContext ServiceContext { get; set; } public ServiceContext ServiceContext { get; set; }
public IFeatureCollection ConnectionFeatures { get; set; }
public IList<IConnectionAdapter> ConnectionAdapters { get; set; } public IList<IConnectionAdapter> ConnectionAdapters { get; set; }
public PipeFactory PipeFactory { get; set; } public PipeFactory PipeFactory { get; set; }
public IPEndPoint LocalEndPoint { get; set; } public IPEndPoint LocalEndPoint { get; set; }

View File

@ -251,13 +251,13 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http
object IFeatureCollection.this[Type key] object IFeatureCollection.this[Type key]
{ {
get => FastFeatureGet(key) ?? ConnectionFeatures?[key]; get => FastFeatureGet(key) ?? ConnectionFeatures[key];
set => FastFeatureSet(key, value); set => FastFeatureSet(key, value);
} }
TFeature IFeatureCollection.Get<TFeature>() TFeature IFeatureCollection.Get<TFeature>()
{ {
return (TFeature)(FastFeatureGet(typeof(TFeature)) ?? ConnectionFeatures?[typeof(TFeature)]); return (TFeature)(FastFeatureGet(typeof(TFeature)) ?? ConnectionFeatures[typeof(TFeature)]);
} }
void IFeatureCollection.Set<TFeature>(TFeature instance) void IFeatureCollection.Set<TFeature>(TFeature instance)
@ -294,7 +294,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http
_wasUpgraded = true; _wasUpgraded = true;
ServiceContext.ConnectionManager.NormalConnectionCount.ReleaseOne(); ConnectionFeatures.Get<IDecrementConcurrentConnectionCountFeature>()?.ReleaseConnection();
StatusCode = StatusCodes.Status101SwitchingProtocols; StatusCode = StatusCodes.Status101SwitchingProtocols;
ReasonPhrase = "Switching Protocols"; ReasonPhrase = "Switching Protocols";

View File

@ -107,7 +107,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http
private IPEndPoint LocalEndPoint => _frameContext.LocalEndPoint; private IPEndPoint LocalEndPoint => _frameContext.LocalEndPoint;
private IPEndPoint RemoteEndPoint => _frameContext.RemoteEndPoint; private IPEndPoint RemoteEndPoint => _frameContext.RemoteEndPoint;
public IFeatureCollection ConnectionFeatures { get; set; } public IFeatureCollection ConnectionFeatures => _frameContext.ConnectionFeatures;
public IPipeReader Input => _frameContext.Transport.Input; public IPipeReader Input => _frameContext.Transport.Input;
public OutputProducer Output { get; } public OutputProducer Output { get; }
public ITimeoutControl TimeoutControl => _frameContext.TimeoutControl; public ITimeoutControl TimeoutControl => _frameContext.TimeoutControl;

View File

@ -3,6 +3,7 @@
using System.IO.Pipelines; using System.IO.Pipelines;
using System.Net; using System.Net;
using Microsoft.AspNetCore.Http.Features;
using Microsoft.AspNetCore.Protocols; using Microsoft.AspNetCore.Protocols;
using Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Infrastructure; using Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Infrastructure;
@ -12,6 +13,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http
{ {
public string ConnectionId { get; set; } public string ConnectionId { get; set; }
public ServiceContext ServiceContext { get; set; } public ServiceContext ServiceContext { get; set; }
public IFeatureCollection ConnectionFeatures { get; set; }
public PipeFactory PipeFactory { get; set; } public PipeFactory PipeFactory { get; set; }
public IPEndPoint RemoteEndPoint { get; set; } public IPEndPoint RemoteEndPoint { get; set; }
public IPEndPoint LocalEndPoint { get; set; } public IPEndPoint LocalEndPoint { get; set; }

View File

@ -43,6 +43,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal
ConnectionId = connectionContext.ConnectionId, ConnectionId = connectionContext.ConnectionId,
FrameConnectionId = frameConnectionId, FrameConnectionId = frameConnectionId,
ServiceContext = _serviceContext, ServiceContext = _serviceContext,
ConnectionFeatures = connectionContext.Features,
PipeFactory = connectionContext.PipeFactory, PipeFactory = connectionContext.PipeFactory,
ConnectionAdapters = _connectionAdapters, ConnectionAdapters = _connectionAdapters,
Transport = connectionContext.Transport, Transport = connectionContext.Transport,

View File

@ -11,23 +11,17 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Infrastructure
private readonly ConcurrentDictionary<long, FrameConnectionReference> _connectionReferences = new ConcurrentDictionary<long, FrameConnectionReference>(); private readonly ConcurrentDictionary<long, FrameConnectionReference> _connectionReferences = new ConcurrentDictionary<long, FrameConnectionReference>();
private readonly IKestrelTrace _trace; private readonly IKestrelTrace _trace;
public FrameConnectionManager(IKestrelTrace trace, long? normalConnectionLimit, long? upgradedConnectionLimit) public FrameConnectionManager(IKestrelTrace trace, long? upgradedConnectionLimit)
: this(trace, GetCounter(normalConnectionLimit), GetCounter(upgradedConnectionLimit)) : this(trace, GetCounter(upgradedConnectionLimit))
{ {
} }
public FrameConnectionManager(IKestrelTrace trace, ResourceCounter normalConnections, ResourceCounter upgradedConnections) public FrameConnectionManager(IKestrelTrace trace, ResourceCounter upgradedConnections)
{ {
NormalConnectionCount = normalConnections;
UpgradedConnectionCount = upgradedConnections; UpgradedConnectionCount = upgradedConnections;
_trace = trace; _trace = trace;
} }
/// <summary>
/// TCP connections processed by Kestrel.
/// </summary>
public ResourceCounter NormalConnectionCount { get; }
/// <summary> /// <summary>
/// Connections that have been switched to a different protocol. /// Connections that have been switched to a different protocol.
/// </summary> /// </summary>

View File

@ -70,7 +70,6 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core
var trace = new KestrelTrace(logger); var trace = new KestrelTrace(logger);
var connectionManager = new FrameConnectionManager( var connectionManager = new FrameConnectionManager(
trace, trace,
serverOptions.Limits.MaxConcurrentConnections,
serverOptions.Limits.MaxConcurrentUpgradedConnections); serverOptions.Limits.MaxConcurrentUpgradedConnections);
var systemClock = new SystemClock(); var systemClock = new SystemClock();
@ -135,16 +134,18 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core
async Task OnBind(ListenOptions endpoint) async Task OnBind(ListenOptions endpoint)
{ {
// Add the connection limit middleware
endpoint.UseConnectionLimit(ServiceContext);
// Configure the user delegate
endpoint.Configure(endpoint);
// Add the HTTP middleware as the terminal connection middleware // Add the HTTP middleware as the terminal connection middleware
endpoint.UseHttpServer(endpoint.ConnectionAdapters, ServiceContext, application); endpoint.UseHttpServer(endpoint.ConnectionAdapters, ServiceContext, application);
var connectionHandler = new ConnectionHandler(ServiceContext, endpoint.Build()); var connectionDelegate = endpoint.Build();
// Add the connection limit middleware
if (Options.Limits.MaxConcurrentConnections.HasValue)
{
connectionDelegate = new ConnectionLimitMiddleware(connectionDelegate, Options.Limits.MaxConcurrentConnections.Value, Trace).OnConnectionAsync;
}
var connectionHandler = new ConnectionHandler(ServiceContext, connectionDelegate);
var transport = _transportFactory.Create(endpoint, connectionHandler); var transport = _transportFactory.Create(endpoint, connectionHandler);
_transports.Add(transport); _transports.Add(transport);

View File

@ -201,7 +201,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core
} }
/// <summary> /// <summary>
/// Gets or sets the maximum number of open HTTP/S connections. When set to null, the number of connections is unlimited. /// Gets or sets the maximum number of open connections. When set to null, the number of connections is unlimited.
/// </summary> /// </summary>
/// <remarks> /// <remarks>
/// <para> /// <para>

View File

@ -100,7 +100,8 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core
throw new ArgumentNullException(nameof(configure)); throw new ArgumentNullException(nameof(configure));
} }
var listenOptions = new ListenOptions(endPoint) { KestrelServerOptions = this, Configure = configure }; var listenOptions = new ListenOptions(endPoint) { KestrelServerOptions = this };
configure(listenOptions);
ListenOptions.Add(listenOptions); ListenOptions.Add(listenOptions);
} }
@ -131,7 +132,8 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core
throw new ArgumentNullException(nameof(configure)); throw new ArgumentNullException(nameof(configure));
} }
var listenOptions = new ListenOptions(socketPath) { KestrelServerOptions = this, Configure = configure }; var listenOptions = new ListenOptions(socketPath) { KestrelServerOptions = this };
configure(listenOptions);
ListenOptions.Add(listenOptions); ListenOptions.Add(listenOptions);
} }
@ -154,7 +156,8 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core
throw new ArgumentNullException(nameof(configure)); throw new ArgumentNullException(nameof(configure));
} }
var listenOptions = new ListenOptions(handle) { KestrelServerOptions = this, Configure = configure }; var listenOptions = new ListenOptions(handle) { KestrelServerOptions = this };
configure(listenOptions);
ListenOptions.Add(listenOptions); ListenOptions.Add(listenOptions);
} }
} }

View File

@ -131,8 +131,6 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core
public IServiceProvider ApplicationServices => KestrelServerOptions?.ApplicationServices; public IServiceProvider ApplicationServices => KestrelServerOptions?.ApplicationServices;
internal Action<ListenOptions> Configure { get; set; } = _ => { };
/// <summary> /// <summary>
/// Gets the name of this endpoint to display on command-line when the web server starts. /// Gets the name of this endpoint to display on command-line when the web server starts.
/// </summary> /// </summary>

View File

@ -18,7 +18,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Tests
{ {
var connectionId = "0"; var connectionId = "0";
var trace = new Mock<IKestrelTrace>(); var trace = new Mock<IKestrelTrace>();
var frameConnectionManager = new FrameConnectionManager(trace.Object, ResourceCounter.Unlimited, ResourceCounter.Unlimited); var frameConnectionManager = new FrameConnectionManager(trace.Object, ResourceCounter.Unlimited);
// Create FrameConnection in inner scope so it doesn't get rooted by the current frame. // Create FrameConnection in inner scope so it doesn't get rooted by the current frame.
UnrootedConnectionsGetRemovedFromHeartbeatInnerScope(connectionId, frameConnectionManager, trace); UnrootedConnectionsGetRemovedFromHeartbeatInnerScope(connectionId, frameConnectionManager, trace);

View File

@ -2,11 +2,10 @@
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. // Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
using System; using System;
using System.IO.Pipelines;
using System.Collections.Generic; using System.Collections.Generic;
using System.Linq; using System.IO.Pipelines;
using System.Threading; using System.Threading;
using System.Threading.Tasks; using Microsoft.AspNetCore.Http.Features;
using Microsoft.AspNetCore.Server.Kestrel.Core.Adapter.Internal; using Microsoft.AspNetCore.Server.Kestrel.Core.Adapter.Internal;
using Microsoft.AspNetCore.Server.Kestrel.Core.Internal; using Microsoft.AspNetCore.Server.Kestrel.Core.Internal;
using Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Infrastructure; using Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Infrastructure;
@ -31,6 +30,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Tests
{ {
ConnectionId = "0123456789", ConnectionId = "0123456789",
ConnectionAdapters = new List<IConnectionAdapter>(), ConnectionAdapters = new List<IConnectionAdapter>(),
ConnectionFeatures = new FeatureCollection(),
PipeFactory = _pipeFactory, PipeFactory = _pipeFactory,
FrameConnectionId = long.MinValue, FrameConnectionId = long.MinValue,
Application = pair.Application, Application = pair.Application,

View File

@ -2,10 +2,11 @@
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. // Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
using System; using System;
using System.IO.Pipelines;
using System.Collections.Generic; using System.Collections.Generic;
using System.Globalization; using System.Globalization;
using System.IO.Pipelines;
using Microsoft.AspNetCore.Http; using Microsoft.AspNetCore.Http;
using Microsoft.AspNetCore.Http.Features;
using Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http; using Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http;
using Microsoft.AspNetCore.Testing; using Microsoft.AspNetCore.Testing;
using Microsoft.Extensions.Primitives; using Microsoft.Extensions.Primitives;
@ -23,6 +24,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Tests
var frameContext = new FrameContext var frameContext = new FrameContext
{ {
ServiceContext = new TestServiceContext(), ServiceContext = new TestServiceContext(),
ConnectionFeatures = new FeatureCollection(),
PipeFactory = factory, PipeFactory = factory,
Application = pair.Application, Application = pair.Application,
Transport = pair.Transport, Transport = pair.Transport,

View File

@ -63,6 +63,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Tests
_frameContext = new FrameContext _frameContext = new FrameContext
{ {
ServiceContext = _serviceContext, ServiceContext = _serviceContext,
ConnectionFeatures = new FeatureCollection(),
PipeFactory = _pipelineFactory, PipeFactory = _pipelineFactory,
TimeoutControl = _timeoutControl.Object, TimeoutControl = _timeoutControl.Object,
Application = pair.Application, Application = pair.Application,

View File

@ -18,9 +18,6 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Tests
d.NoDelay = false; d.NoDelay = false;
}); });
// Execute the callback
o1.ListenOptions[1].Configure(o1.ListenOptions[1]);
Assert.True(o1.ListenOptions[0].NoDelay); Assert.True(o1.ListenOptions[0].NoDelay);
Assert.False(o1.ListenOptions[1].NoDelay); Assert.False(o1.ListenOptions[1].NoDelay);
} }

View File

@ -4,6 +4,7 @@
using System; using System;
using System.IO.Pipelines; using System.IO.Pipelines;
using System.Text; using System.Text;
using Microsoft.AspNetCore.Http.Features;
using Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http; using Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http;
using Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Infrastructure; using Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Infrastructure;
using Microsoft.AspNetCore.Testing; using Microsoft.AspNetCore.Testing;
@ -27,6 +28,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Tests
FrameContext = new FrameContext FrameContext = new FrameContext
{ {
ServiceContext = new TestServiceContext(), ServiceContext = new TestServiceContext(),
ConnectionFeatures = new FeatureCollection(),
Application = Application, Application = Application,
Transport = Transport, Transport = Transport,
PipeFactory = _pipelineFactory, PipeFactory = _pipelineFactory,

View File

@ -2,12 +2,13 @@
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. // Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
using System; using System;
using System.IO; using System.Net;
using System.Net.Sockets;
using System.Threading; using System.Threading;
using System.Threading.Tasks; using System.Threading.Tasks;
using Microsoft.AspNetCore.Http; using Microsoft.AspNetCore.Http;
using Microsoft.AspNetCore.Http.Features; using Microsoft.AspNetCore.Http.Features;
using Microsoft.AspNetCore.Server.Kestrel.Core;
using Microsoft.AspNetCore.Server.Kestrel.Core.Internal;
using Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Infrastructure; using Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Infrastructure;
using Microsoft.AspNetCore.Server.Kestrel.Tests; using Microsoft.AspNetCore.Server.Kestrel.Tests;
using Microsoft.AspNetCore.Testing; using Microsoft.AspNetCore.Testing;
@ -23,15 +24,15 @@ namespace Microsoft.AspNetCore.Server.Kestrel.FunctionalTests
var requestTcs = new TaskCompletionSource<object>(); var requestTcs = new TaskCompletionSource<object>();
var releasedTcs = new TaskCompletionSource<object>(); var releasedTcs = new TaskCompletionSource<object>();
var lockedTcs = new TaskCompletionSource<bool>(); var lockedTcs = new TaskCompletionSource<bool>();
var (serviceContext, counter) = SetupMaxConnections(max: 1); var counter = new EventRaisingResourceCounter(ResourceCounter.Quota(1));
counter.OnLock += (s, e) => lockedTcs.TrySetResult(e); counter.OnLock += (s, e) => lockedTcs.TrySetResult(e);
counter.OnRelease += (s, e) => releasedTcs.TrySetResult(null); counter.OnRelease += (s, e) => releasedTcs.TrySetResult(null);
using (var server = new TestServer(async context => using (var server = CreateServerWithMaxConnections(async context =>
{ {
await context.Response.WriteAsync("Hello"); await context.Response.WriteAsync("Hello");
await requestTcs.Task; await requestTcs.Task;
}, serviceContext)) }, counter))
using (var connection = server.CreateConnection()) using (var connection = server.CreateConnection())
{ {
await connection.SendEmptyGetAsKeepAlive(); ; await connection.SendEmptyGetAsKeepAlive(); ;
@ -46,8 +47,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.FunctionalTests
[Fact] [Fact]
public async Task UpgradedConnectionsCountsAgainstDifferentLimit() public async Task UpgradedConnectionsCountsAgainstDifferentLimit()
{ {
var (serviceContext, _) = SetupMaxConnections(max: 1); using (var server = CreateServerWithMaxConnections(async context =>
using (var server = new TestServer(async context =>
{ {
var feature = context.Features.Get<IHttpUpgradeFeature>(); var feature = context.Features.Get<IHttpUpgradeFeature>();
if (feature.IsUpgradableRequest) if (feature.IsUpgradableRequest)
@ -59,7 +59,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.FunctionalTests
await Task.Delay(100); await Task.Delay(100);
} }
} }
}, serviceContext)) }, max: 1))
using (var disposables = new DisposableStack<TestConnection>()) using (var disposables = new DisposableStack<TestConnection>())
{ {
var upgraded = server.CreateConnection(); var upgraded = server.CreateConnection();
@ -81,7 +81,8 @@ namespace Microsoft.AspNetCore.Server.Kestrel.FunctionalTests
{ {
// this may throw IOException, depending on how fast Kestrel closes the socket // this may throw IOException, depending on how fast Kestrel closes the socket
await rejected.SendEmptyGetAsKeepAlive(); await rejected.SendEmptyGetAsKeepAlive();
} catch { } }
catch { }
// connection should close without sending any data // connection should close without sending any data
await rejected.WaitForConnectionClose().TimeoutAfter(TimeSpan.FromSeconds(15)); await rejected.WaitForConnectionClose().TimeoutAfter(TimeSpan.FromSeconds(15));
@ -93,14 +94,13 @@ namespace Microsoft.AspNetCore.Server.Kestrel.FunctionalTests
public async Task RejectsConnectionsWhenLimitReached() public async Task RejectsConnectionsWhenLimitReached()
{ {
const int max = 10; const int max = 10;
var (serviceContext, _) = SetupMaxConnections(max);
var requestTcs = new TaskCompletionSource<object>(); var requestTcs = new TaskCompletionSource<object>();
using (var server = new TestServer(async context => using (var server = CreateServerWithMaxConnections(async context =>
{ {
await context.Response.WriteAsync("Hello"); await context.Response.WriteAsync("Hello");
await requestTcs.Task; await requestTcs.Task;
}, serviceContext)) }, max))
using (var disposables = new DisposableStack<TestConnection>()) using (var disposables = new DisposableStack<TestConnection>())
{ {
for (var i = 0; i < max; i++) for (var i = 0; i < max; i++)
@ -141,7 +141,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.FunctionalTests
var openedTcs = new TaskCompletionSource<object>(); var openedTcs = new TaskCompletionSource<object>();
var closedTcs = new TaskCompletionSource<object>(); var closedTcs = new TaskCompletionSource<object>();
var (serviceContext, counter) = SetupMaxConnections(uint.MaxValue); var counter = new EventRaisingResourceCounter(ResourceCounter.Quota(uint.MaxValue));
counter.OnLock += (o, e) => counter.OnLock += (o, e) =>
{ {
@ -159,7 +159,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.FunctionalTests
} }
}; };
using (var server = new TestServer(_ => Task.CompletedTask, serviceContext)) using (var server = CreateServerWithMaxConnections(_ => Task.CompletedTask, counter))
{ {
// open a bunch of connections in parallel // open a bunch of connections in parallel
Parallel.For(0, count, async i => Parallel.For(0, count, async i =>
@ -187,12 +187,24 @@ namespace Microsoft.AspNetCore.Server.Kestrel.FunctionalTests
} }
} }
private (TestServiceContext serviceContext, EventRaisingResourceCounter counter) SetupMaxConnections(long max) private TestServer CreateServerWithMaxConnections(RequestDelegate app, long max)
{ {
var counter = new EventRaisingResourceCounter(ResourceCounter.Quota(max));
var serviceContext = new TestServiceContext(); var serviceContext = new TestServiceContext();
serviceContext.ConnectionManager = new FrameConnectionManager(serviceContext.Log, counter, ResourceCounter.Unlimited); serviceContext.ServerOptions.Limits.MaxConcurrentConnections = max;
return (serviceContext, counter); return new TestServer(app, serviceContext);
}
private TestServer CreateServerWithMaxConnections(RequestDelegate app, ResourceCounter concurrentConnectionCounter)
{
var serviceContext = new TestServiceContext();
var listenOptions = new ListenOptions(new IPEndPoint(IPAddress.Loopback, 0));
listenOptions.Use(next =>
{
var middleware = new ConnectionLimitMiddleware(next, concurrentConnectionCounter, serviceContext.Log);
return middleware.OnConnectionAsync;
});
return new TestServer(app, serviceContext, listenOptions);
} }
} }
} }

View File

@ -14,7 +14,6 @@ using Microsoft.AspNetCore.Server.Kestrel.Core;
using Microsoft.AspNetCore.Server.Kestrel.Transport.Abstractions.Internal; using Microsoft.AspNetCore.Server.Kestrel.Transport.Abstractions.Internal;
using Microsoft.AspNetCore.Testing; using Microsoft.AspNetCore.Testing;
using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Logging;
using Microsoft.Extensions.Options; using Microsoft.Extensions.Options;
namespace Microsoft.AspNetCore.Server.Kestrel.FunctionalTests namespace Microsoft.AspNetCore.Server.Kestrel.FunctionalTests

View File

@ -260,7 +260,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.FunctionalTests
const int limit = 10; const int limit = 10;
var upgradeTcs = new TaskCompletionSource<object>(); var upgradeTcs = new TaskCompletionSource<object>();
var serviceContext = new TestServiceContext(); var serviceContext = new TestServiceContext();
serviceContext.ConnectionManager = new FrameConnectionManager(serviceContext.Log, ResourceCounter.Unlimited, ResourceCounter.Quota(limit)); serviceContext.ConnectionManager = new FrameConnectionManager(serviceContext.Log, ResourceCounter.Quota(limit));
using (var server = new TestServer(async context => using (var server = new TestServer(async context =>
{ {

View File

@ -6,6 +6,7 @@ using System.Collections.Concurrent;
using System.IO.Pipelines; using System.IO.Pipelines;
using System.Threading; using System.Threading;
using System.Threading.Tasks; using System.Threading.Tasks;
using Microsoft.AspNetCore.Http.Features;
using Microsoft.AspNetCore.Server.Kestrel.Core; using Microsoft.AspNetCore.Server.Kestrel.Core;
using Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http; using Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http;
using Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Infrastructure; using Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Infrastructure;
@ -699,6 +700,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Transport.Libuv.Tests
var frame = new Frame<object>(null, new FrameContext var frame = new Frame<object>(null, new FrameContext
{ {
ServiceContext = serviceContext, ServiceContext = serviceContext,
ConnectionFeatures = new FeatureCollection(),
PipeFactory = _pipeFactory, PipeFactory = _pipeFactory,
TimeoutControl = Mock.Of<ITimeoutControl>(), TimeoutControl = Mock.Of<ITimeoutControl>(),
Application = pair.Application, Application = pair.Application,

View File

@ -38,7 +38,7 @@ namespace Microsoft.AspNetCore.Testing
ThreadPool = new LoggingThreadPool(Log); ThreadPool = new LoggingThreadPool(Log);
SystemClock = new MockSystemClock(); SystemClock = new MockSystemClock();
DateHeaderValueManager = new DateHeaderValueManager(SystemClock); DateHeaderValueManager = new DateHeaderValueManager(SystemClock);
ConnectionManager = new FrameConnectionManager(Log, ResourceCounter.Unlimited, ResourceCounter.Unlimited); ConnectionManager = new FrameConnectionManager(Log, ResourceCounter.Unlimited);
HttpParserFactory = frameAdapter => new HttpParser<FrameAdapter>(frameAdapter.Frame.ServiceContext.Log.IsEnabled(LogLevel.Information)); HttpParserFactory = frameAdapter => new HttpParser<FrameAdapter>(frameAdapter.Frame.ServiceContext.Log.IsEnabled(LogLevel.Information));
ServerOptions = new KestrelServerOptions ServerOptions = new KestrelServerOptions
{ {