This repository was archived by the owner on Dec 18, 2018. It is now read-only.
-
Notifications
You must be signed in to change notification settings - Fork 522
Added initial connection middleware pipeline #2003
Merged
Merged
Changes from all commits
Commits
Show all changes
5 commits
Select commit
Hold shift + click to select a range
2e55815
Added initial connection middleware pipeline
davidfowl aeb3efb
Fixed benchmarks
davidfowl 5c6786b
Removed IConnectionApplicationFeature
davidfowl 52bca8a
Fixed the application pipe connection
davidfowl fa8d38b
PR feedback
davidfowl File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,31 +1,27 @@ | ||
// 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; | ||
using System.IO.Pipelines; | ||
using System.Net; | ||
using System.Threading; | ||
using Microsoft.AspNetCore.Hosting.Server; | ||
using System.Threading.Tasks; | ||
using Microsoft.AspNetCore.Http.Features; | ||
using Microsoft.AspNetCore.Protocols; | ||
using Microsoft.AspNetCore.Protocols.Features; | ||
using Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Infrastructure; | ||
using Microsoft.AspNetCore.Server.Kestrel.Transport.Abstractions.Internal; | ||
using Microsoft.Extensions.Logging; | ||
|
||
namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal | ||
{ | ||
public class ConnectionHandler<TContext> : IConnectionHandler | ||
public class ConnectionHandler : IConnectionHandler | ||
{ | ||
private static long _lastFrameConnectionId = long.MinValue; | ||
|
||
private readonly ListenOptions _listenOptions; | ||
private readonly ServiceContext _serviceContext; | ||
private readonly IHttpApplication<TContext> _application; | ||
private readonly ConnectionDelegate _connectionDelegate; | ||
|
||
public ConnectionHandler(ListenOptions listenOptions, ServiceContext serviceContext, IHttpApplication<TContext> application) | ||
public ConnectionHandler(ServiceContext serviceContext, ConnectionDelegate connectionDelegate) | ||
{ | ||
_listenOptions = listenOptions; | ||
_serviceContext = serviceContext; | ||
_application = application; | ||
_connectionDelegate = connectionDelegate; | ||
} | ||
|
||
public void OnConnection(IFeatureCollection features) | ||
|
@@ -34,89 +30,57 @@ public void OnConnection(IFeatureCollection features) | |
|
||
var transportFeature = connectionContext.Features.Get<IConnectionTransportFeature>(); | ||
|
||
var inputPipe = transportFeature.PipeFactory.Create(GetInputPipeOptions(transportFeature.InputWriterScheduler)); | ||
var outputPipe = transportFeature.PipeFactory.Create(GetOutputPipeOptions(transportFeature.OutputReaderScheduler)); | ||
// REVIEW: Unfortunately, we still need to use the service context to create the pipes since the settings | ||
// for the scheduler and limits are specified here | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Could you just pass in the KestrelServerOptions? Do you need to pass in a scheduler and the "ThreadPool"? |
||
var inputOptions = GetInputPipeOptions(_serviceContext, transportFeature.InputWriterScheduler); | ||
var outputOptions = GetOutputPipeOptions(_serviceContext, transportFeature.OutputReaderScheduler); | ||
|
||
var connectionId = CorrelationIdGenerator.GetNextId(); | ||
var frameConnectionId = Interlocked.Increment(ref _lastFrameConnectionId); | ||
var pair = connectionContext.PipeFactory.CreateConnectionPair(inputOptions, outputOptions); | ||
|
||
// Set the transport and connection id | ||
connectionContext.ConnectionId = connectionId; | ||
transportFeature.Connection = new PipeConnection(inputPipe.Reader, outputPipe.Writer); | ||
var applicationConnection = new PipeConnection(outputPipe.Reader, inputPipe.Writer); | ||
|
||
if (!_serviceContext.ConnectionManager.NormalConnectionCount.TryLockOne()) | ||
{ | ||
var goAway = new RejectionConnection(inputPipe, outputPipe, connectionId, _serviceContext) | ||
{ | ||
Connection = applicationConnection | ||
}; | ||
|
||
connectionContext.Features.Set<IConnectionApplicationFeature>(goAway); | ||
connectionContext.ConnectionId = CorrelationIdGenerator.GetNextId(); | ||
connectionContext.Transport = pair.Transport; | ||
|
||
goAway.Reject(); | ||
return; | ||
} | ||
|
||
var frameConnectionContext = new FrameConnectionContext | ||
{ | ||
ConnectionId = connectionId, | ||
FrameConnectionId = frameConnectionId, | ||
ServiceContext = _serviceContext, | ||
PipeFactory = connectionContext.PipeFactory, | ||
ConnectionAdapters = _listenOptions.ConnectionAdapters, | ||
Input = inputPipe, | ||
Output = outputPipe | ||
}; | ||
// This *must* be set before returning from OnConnection | ||
transportFeature.Application = pair.Application; | ||
|
||
var connectionFeature = connectionContext.Features.Get<IHttpConnectionFeature>(); | ||
// REVIEW: This task should be tracked by the server for graceful shutdown | ||
// Today it's handled specifically for http but not for aribitrary middleware | ||
_ = Execute(connectionContext); | ||
} | ||
|
||
if (connectionFeature != null) | ||
private async Task Execute(ConnectionContext connectionContext) | ||
{ | ||
try | ||
{ | ||
if (connectionFeature.LocalIpAddress != null) | ||
{ | ||
frameConnectionContext.LocalEndPoint = new IPEndPoint(connectionFeature.LocalIpAddress, connectionFeature.LocalPort); | ||
} | ||
|
||
if (connectionFeature.RemoteIpAddress != null) | ||
{ | ||
frameConnectionContext.RemoteEndPoint = new IPEndPoint(connectionFeature.RemoteIpAddress, connectionFeature.RemotePort); | ||
} | ||
await _connectionDelegate(connectionContext); | ||
} | ||
|
||
var connection = new FrameConnection(frameConnectionContext) | ||
catch (Exception ex) | ||
{ | ||
Connection = applicationConnection | ||
}; | ||
|
||
connectionContext.Features.Set<IConnectionApplicationFeature>(connection); | ||
|
||
// Since data cannot be added to the inputPipe by the transport until OnConnection returns, | ||
// Frame.ProcessRequestsAsync is guaranteed to unblock the transport thread before calling | ||
// application code. | ||
connection.StartRequestProcessing(_application); | ||
_serviceContext.Log.LogCritical(0, ex, $"{nameof(ConnectionHandler)}.{nameof(Execute)}() {connectionContext.ConnectionId}"); | ||
} | ||
} | ||
|
||
// Internal for testing | ||
internal PipeOptions GetInputPipeOptions(IScheduler writerScheduler) => new PipeOptions | ||
internal static PipeOptions GetInputPipeOptions(ServiceContext serviceContext, IScheduler writerScheduler) => new PipeOptions | ||
{ | ||
ReaderScheduler = _serviceContext.ThreadPool, | ||
ReaderScheduler = serviceContext.ThreadPool, | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Maybe we should pass the reader scheduler explicitly like the writer scheduler. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Sure, eventually. |
||
WriterScheduler = writerScheduler, | ||
MaximumSizeHigh = _serviceContext.ServerOptions.Limits.MaxRequestBufferSize ?? 0, | ||
MaximumSizeLow = _serviceContext.ServerOptions.Limits.MaxRequestBufferSize ?? 0 | ||
MaximumSizeHigh = serviceContext.ServerOptions.Limits.MaxRequestBufferSize ?? 0, | ||
MaximumSizeLow = serviceContext.ServerOptions.Limits.MaxRequestBufferSize ?? 0 | ||
}; | ||
|
||
internal PipeOptions GetOutputPipeOptions(IScheduler readerScheduler) => new PipeOptions | ||
internal static PipeOptions GetOutputPipeOptions(ServiceContext serviceContext, IScheduler readerScheduler) => new PipeOptions | ||
{ | ||
ReaderScheduler = readerScheduler, | ||
WriterScheduler = _serviceContext.ThreadPool, | ||
MaximumSizeHigh = GetOutputResponseBufferSize(), | ||
MaximumSizeLow = GetOutputResponseBufferSize() | ||
WriterScheduler = serviceContext.ThreadPool, | ||
MaximumSizeHigh = GetOutputResponseBufferSize(serviceContext), | ||
MaximumSizeLow = GetOutputResponseBufferSize(serviceContext) | ||
}; | ||
|
||
private long GetOutputResponseBufferSize() | ||
private static long GetOutputResponseBufferSize(ServiceContext serviceContext) | ||
{ | ||
var bufferSize = _serviceContext.ServerOptions.Limits.MaxResponseBufferSize; | ||
var bufferSize = serviceContext.ServerOptions.Limits.MaxResponseBufferSize; | ||
if (bufferSize == 0) | ||
{ | ||
// 0 = no buffering so we need to configure the pipe so the the writer waits on the reader directly | ||
|
16 changes: 16 additions & 0 deletions
16
src/Kestrel.Core/Internal/ConnectionLimitBuilderExtensions.cs
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,16 @@ | ||
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; | ||
}); | ||
} | ||
} | ||
} |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,32 @@ | ||
using System.Threading.Tasks; | ||
using Microsoft.AspNetCore.Protocols; | ||
using Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Infrastructure; | ||
|
||
namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal | ||
{ | ||
public class ConnectionLimitMiddleware | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 👍 |
||
{ | ||
private readonly ServiceContext _serviceContext; | ||
private readonly ConnectionDelegate _next; | ||
|
||
public ConnectionLimitMiddleware(ConnectionDelegate next, ServiceContext serviceContext) | ||
{ | ||
_next = next; | ||
_serviceContext = serviceContext; | ||
} | ||
|
||
public Task OnConnectionAsync(ConnectionContext connection) | ||
{ | ||
if (!_serviceContext.ConnectionManager.NormalConnectionCount.TryLockOne()) | ||
{ | ||
KestrelEventSource.Log.ConnectionRejected(connection.ConnectionId); | ||
_serviceContext.Log.ConnectionRejected(connection.ConnectionId); | ||
connection.Transport.Input.Complete(); | ||
connection.Transport.Output.Complete(); | ||
return Task.CompletedTask; | ||
} | ||
|
||
return _next(connection); | ||
} | ||
} | ||
} |
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think you want to use
_outputPipe
here so the Cleanup method works.There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Good catch.