Simplified HttpConnection (#11488)

- Don't register callbacks until we have a request processor
- Remove the IConnectionTimeoutFeature from the feature collection, it was used by connection adapters but isn't needed anymore.
- Rename OnInputOrOutputCompleted to OnConnectionClosed
This commit is contained in:
David Fowler 2019-06-24 11:42:27 -07:00 committed by GitHub
parent 7d4258b186
commit c63cd0c3ad
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
1 changed files with 57 additions and 78 deletions

View File

@ -3,8 +3,6 @@
using System; using System;
using System.Diagnostics; using System.Diagnostics;
using System.IO.Pipelines;
using System.Net;
using System.Threading.Tasks; using System.Threading.Tasks;
using Microsoft.AspNetCore.Connections; using Microsoft.AspNetCore.Connections;
using Microsoft.AspNetCore.Connections.Features; using Microsoft.AspNetCore.Connections.Features;
@ -39,80 +37,77 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal
_timeoutControl = new TimeoutControl(this); _timeoutControl = new TimeoutControl(this);
} }
public string ConnectionId => _context.ConnectionId;
public IPEndPoint LocalEndPoint => _context.LocalEndPoint;
public IPEndPoint RemoteEndPoint => _context.RemoteEndPoint;
private IKestrelTrace Log => _context.ServiceContext.Log; private IKestrelTrace Log => _context.ServiceContext.Log;
public async Task ProcessRequestsAsync<TContext>(IHttpApplication<TContext> httpApplication) public async Task ProcessRequestsAsync<TContext>(IHttpApplication<TContext> httpApplication)
{ {
try try
{ {
// This feature should never be null in Kestrel // Ensure TimeoutControl._lastTimestamp is initialized before anything that could set timeouts runs.
var connectionHeartbeatFeature = _context.ConnectionFeatures.Get<IConnectionHeartbeatFeature>(); _timeoutControl.Initialize(_systemClock.UtcNowTicks);
Debug.Assert(connectionHeartbeatFeature != null, nameof(IConnectionHeartbeatFeature) + " is missing!"); IRequestProcessor requestProcessor = null;
connectionHeartbeatFeature?.OnHeartbeat(state => ((HttpConnection)state).Tick(), this); var httpConnectionContext = new HttpConnectionContext
var connectionLifetimeNotificationFeature = _context.ConnectionFeatures.Get<IConnectionLifetimeNotificationFeature>();
Debug.Assert(connectionLifetimeNotificationFeature != null, nameof(IConnectionLifetimeNotificationFeature) + " is missing!");
using (connectionLifetimeNotificationFeature?.ConnectionClosedRequested.Register(state => ((HttpConnection)state).StopProcessingNextRequest(), this))
{ {
// Ensure TimeoutControl._lastTimestamp is initialized before anything that could set timeouts runs. ConnectionId = _context.ConnectionId,
_timeoutControl.Initialize(_systemClock.UtcNowTicks); ConnectionFeatures = _context.ConnectionFeatures,
MemoryPool = _context.MemoryPool,
LocalEndPoint = _context.LocalEndPoint,
RemoteEndPoint = _context.RemoteEndPoint,
ServiceContext = _context.ServiceContext,
ConnectionContext = _context.ConnectionContext,
TimeoutControl = _timeoutControl,
Transport = _context.Transport
};
_context.ConnectionFeatures.Set<IConnectionTimeoutFeature>(_timeoutControl); switch (SelectProtocol())
{
case HttpProtocols.Http1:
// _http1Connection must be initialized before adding the connection to the connection manager
requestProcessor = _http1Connection = new Http1Connection(httpConnectionContext);
_protocolSelectionState = ProtocolSelectionState.Selected;
break;
case HttpProtocols.Http2:
// _http2Connection must be initialized before yielding control to the transport thread,
// to prevent a race condition where _http2Connection.Abort() is called just as
// _http2Connection is about to be initialized.
requestProcessor = new Http2Connection(httpConnectionContext);
_protocolSelectionState = ProtocolSelectionState.Selected;
break;
case HttpProtocols.None:
// An error was already logged in SelectProtocol(), but we should close the connection.
Abort(new ConnectionAbortedException(CoreStrings.ProtocolSelectionFailed));
break;
default:
// SelectProtocol() only returns Http1, Http2 or None.
throw new NotSupportedException($"{nameof(SelectProtocol)} returned something other than Http1, Http2 or None.");
}
IRequestProcessor requestProcessor = null; _requestProcessor = requestProcessor;
lock (_protocolSelectionLock) if (requestProcessor != null)
{ {
// Ensure that the connection hasn't already been stopped. var connectionHeartbeatFeature = _context.ConnectionFeatures.Get<IConnectionHeartbeatFeature>();
if (_protocolSelectionState == ProtocolSelectionState.Initializing) var connectionLifetimeNotificationFeature = _context.ConnectionFeatures.Get<IConnectionLifetimeNotificationFeature>();
{
var derivedContext = CreateDerivedContext(_context.Transport);
switch (SelectProtocol()) // These features should never be null in Kestrel itself, if this middleware is ever refactored to run outside of kestrel,
{ // we'll need to handle these missing.
case HttpProtocols.Http1: Debug.Assert(connectionHeartbeatFeature != null, nameof(IConnectionHeartbeatFeature) + " is missing!");
// _http1Connection must be initialized before adding the connection to the connection manager Debug.Assert(connectionLifetimeNotificationFeature != null, nameof(IConnectionLifetimeNotificationFeature) + " is missing!");
requestProcessor = _http1Connection = new Http1Connection(derivedContext);
_protocolSelectionState = ProtocolSelectionState.Selected;
break;
case HttpProtocols.Http2:
// _http2Connection must be initialized before yielding control to the transport thread,
// to prevent a race condition where _http2Connection.Abort() is called just as
// _http2Connection is about to be initialized.
requestProcessor = new Http2Connection(derivedContext);
_protocolSelectionState = ProtocolSelectionState.Selected;
break;
case HttpProtocols.None:
// An error was already logged in SelectProtocol(), but we should close the connection.
Abort(new ConnectionAbortedException(CoreStrings.ProtocolSelectionFailed));
break;
default:
// SelectProtocol() only returns Http1, Http2 or None.
throw new NotSupportedException($"{nameof(SelectProtocol)} returned something other than Http1, Http2 or None.");
}
_requestProcessor = requestProcessor; // Register the various callbacks once we're going to start processing requests
}
}
var closedRegistration = _context.ConnectionContext.ConnectionClosed.Register(state => ((HttpConnection)state).OnInputOrOutputCompleted(), this); // The heart beat for various timeouts
connectionHeartbeatFeature?.OnHeartbeat(state => ((HttpConnection)state).Tick(), this);
// We don't care about callbacks once all requests are processed // Register for graceful shutdown of the server
using (closedRegistration) using var shutdownRegistration = connectionLifetimeNotificationFeature?.ConnectionClosedRequested.Register(state => ((HttpConnection)state).StopProcessingNextRequest(), this);
{
if (requestProcessor != null) // Register for connection close
{ using var closedRegistration = _context.ConnectionContext.ConnectionClosed.Register(state => ((HttpConnection)state).OnConnectionClosed(), this);
await requestProcessor.ProcessRequestsAsync(httpApplication);
} await requestProcessor.ProcessRequestsAsync(httpApplication);
}
} }
} }
catch (Exception ex) catch (Exception ex)
@ -136,22 +131,6 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal
_protocolSelectionState = ProtocolSelectionState.Selected; _protocolSelectionState = ProtocolSelectionState.Selected;
} }
private HttpConnectionContext CreateDerivedContext(IDuplexPipe transport)
{
return new HttpConnectionContext
{
ConnectionId = _context.ConnectionId,
ConnectionFeatures = _context.ConnectionFeatures,
MemoryPool = _context.MemoryPool,
LocalEndPoint = _context.LocalEndPoint,
RemoteEndPoint = _context.RemoteEndPoint,
ServiceContext = _context.ServiceContext,
ConnectionContext = _context.ConnectionContext,
TimeoutControl = _timeoutControl,
Transport = transport
};
}
private void StopProcessingNextRequest() private void StopProcessingNextRequest()
{ {
ProtocolSelectionState previousState; ProtocolSelectionState previousState;
@ -183,7 +162,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal
} }
} }
private void OnInputOrOutputCompleted() private void OnConnectionClosed()
{ {
ProtocolSelectionState previousState; ProtocolSelectionState previousState;
lock (_protocolSelectionLock) lock (_protocolSelectionLock)
@ -287,7 +266,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal
// It's safe to use UtcNowUnsynchronized since Tick is called by the Heartbeat. // It's safe to use UtcNowUnsynchronized since Tick is called by the Heartbeat.
var now = _systemClock.UtcNowUnsynchronized; var now = _systemClock.UtcNowUnsynchronized;
_timeoutControl.Tick(now); _timeoutControl.Tick(now);
_requestProcessor?.Tick(now); _requestProcessor.Tick(now);
} }
public void OnTimeout(TimeoutReason reason) public void OnTimeout(TimeoutReason reason)