Lazily allocate protocol-specific connection objects (#2190)

* Refactor Http[12]?Connection
This commit is contained in:
Stephen Halter 2017-12-04 15:59:12 -08:00 committed by GitHub
parent b8a1c04ffb
commit 668f8e3b4b
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
21 changed files with 206 additions and 244 deletions

View File

@ -18,7 +18,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Performance
private const int InnerLoopCount = 512;
public ReadableBuffer _buffer;
public Http1Connection<object> _http1Connection;
public Http1Connection _http1Connection;
[IterationSetup]
public void Setup()
@ -32,7 +32,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Performance
HttpParser = NullParser<Http1ParsingHandler>.Instance
};
var http1Connection = new Http1Connection<object>(application: null, context: new Http1ConnectionContext
var http1Connection = new Http1Connection(new Http1ConnectionContext
{
ServiceContext = serviceContext,
ConnectionFeatures = new FeatureCollection(),

View File

@ -25,7 +25,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Performance
private static readonly Task _psuedoAsyncTask = Task.FromResult(27);
private static readonly Func<object, Task> _psuedoAsyncTaskFunc = (obj) => _psuedoAsyncTask;
private readonly TestHttp1Connection<object> _http1Connection;
private readonly TestHttp1Connection _http1Connection;
private (IPipeConnection Transport, IPipeConnection Application) _pair;
private readonly byte[] _writeData;
@ -92,7 +92,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Performance
return _http1Connection.ResponseBody.WriteAsync(_writeData, 0, _writeData.Length, default(CancellationToken));
}
private TestHttp1Connection<object> MakeHttp1Connection()
private TestHttp1Connection MakeHttp1Connection()
{
using (var memoryPool = new MemoryPool())
{
@ -107,15 +107,14 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Performance
HttpParser = new HttpParser<Http1ParsingHandler>()
};
var http1Connection = new TestHttp1Connection<object>(
application: null, context: new Http1ConnectionContext
{
ServiceContext = serviceContext,
ConnectionFeatures = new FeatureCollection(),
BufferPool = memoryPool,
Application = pair.Application,
Transport = pair.Transport
});
var http1Connection = new TestHttp1Connection(new Http1ConnectionContext
{
ServiceContext = serviceContext,
ConnectionFeatures = new FeatureCollection(),
BufferPool = memoryPool,
Application = pair.Application,
Transport = pair.Transport
});
http1Connection.Reset();
http1Connection.InitializeStreams(MessageBody.ZeroContentLengthKeepAlive);

View File

@ -15,7 +15,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Performance
[ParameterizedJobConfig(typeof(CoreConfig))]
public class HttpProtocolFeatureCollection
{
private readonly Http1Connection<object> _http1Connection;
private readonly Http1Connection _http1Connection;
private IFeatureCollection _collection;
[Benchmark(Baseline = true)]
@ -89,7 +89,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Performance
HttpParser = new HttpParser<Http1ParsingHandler>()
};
var http1Connection = new Http1Connection<object>(application: null, context: new Http1ConnectionContext
var http1Connection = new Http1Connection(new Http1ConnectionContext
{
ServiceContext = serviceContext,
ConnectionFeatures = new FeatureCollection(),

View File

@ -17,7 +17,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Performance
{
public IPipe Pipe { get; set; }
public Http1Connection<object> Http1Connection { get; set; }
public Http1Connection Http1Connection { get; set; }
[IterationSetup]
public void Setup()
@ -33,7 +33,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Performance
HttpParser = new HttpParser<Http1ParsingHandler>()
};
var http1Connection = new Http1Connection<object>(application: null, context: new Http1ConnectionContext
var http1Connection = new Http1Connection(new Http1ConnectionContext
{
ServiceContext = serviceContext,
ConnectionFeatures = new FeatureCollection(),

View File

@ -182,7 +182,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Performance
HttpParser = new HttpParser<Http1ParsingHandler>()
};
var http1Connection = new Http1Connection<object>(application: null, context: new Http1ConnectionContext
var http1Connection = new Http1Connection(new Http1ConnectionContext
{
ServiceContext = serviceContext,
ConnectionFeatures = new FeatureCollection(),

View File

@ -22,7 +22,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Performance
{
private static readonly byte[] _helloWorldPayload = Encoding.ASCII.GetBytes("Hello, World!");
private TestHttp1Connection<object> _http1Connection;
private TestHttp1Connection _http1Connection;
[Params(
BenchmarkTypes.TechEmpowerPlaintext,
@ -122,16 +122,15 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Performance
HttpParser = new HttpParser<Http1ParsingHandler>()
};
var http1Connection = new TestHttp1Connection<object>(
application: null, context: new Http1ConnectionContext
{
ServiceContext = serviceContext,
ConnectionFeatures = new FeatureCollection(),
BufferPool = bufferPool,
TimeoutControl = new MockTimeoutControl(),
Application = pair.Application,
Transport = pair.Transport
});
var http1Connection = new TestHttp1Connection(new Http1ConnectionContext
{
ServiceContext = serviceContext,
ConnectionFeatures = new FeatureCollection(),
BufferPool = bufferPool,
TimeoutControl = new MockTimeoutControl(),
Application = pair.Application,
Transport = pair.Transport
});
http1Connection.Reset();

View File

@ -6,13 +6,12 @@ using System.Diagnostics;
using System.IO.Pipelines;
using System.Text;
using System.Text.Encodings.Web.Utf8;
using System.Threading.Tasks;
using Microsoft.AspNetCore.Http.Features;
using Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Infrastructure;
namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http
{
public abstract partial class Http1Connection : HttpProtocol
public partial class Http1Connection : HttpProtocol, IRequestProcessor
{
private const byte ByteAsterisk = (byte)'*';
private const byte ByteForwardSlash = (byte)'/';
@ -146,6 +145,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http
{
TimeoutControl.CancelTimeout();
}
return result;
}
@ -337,7 +337,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http
QueryString = query.GetAsciiStringNonNullCharacters();
}
private unsafe static string GetUtf8String(Span<byte> path)
private static unsafe string GetUtf8String(Span<byte> path)
{
// .NET 451 doesn't have pointer overloads for Encoding.GetString so we
// copy to an array
@ -347,7 +347,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http
}
}
protected void EnsureHostHeaderExists()
private void EnsureHostHeaderExists()
{
if (_httpVersion == Http.HttpVersion.Http10)
{

View File

@ -1,27 +0,0 @@
// 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.Hosting.Server;
namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http
{
public class Http1Connection<TContext> : Http1Connection
{
private readonly IHttpApplication<TContext> _application;
private TContext _httpContext;
public Http1Connection(IHttpApplication<TContext> application, Http1ConnectionContext context)
: base(context)
{
_application = application;
}
protected override void CreateHttpContext() => _httpContext = _application.CreateContext(this);
protected override void DisposeHttpContext() => _application.DisposeContext(_httpContext, _applicationException);
protected override Task InvokeApplicationAsync() => _application.ProcessRequestAsync(_httpContext);
}
}

View File

@ -12,6 +12,7 @@ using System.Runtime.CompilerServices;
using System.Text;
using System.Threading;
using System.Threading.Tasks;
using Microsoft.AspNetCore.Hosting.Server;
using Microsoft.AspNetCore.Http;
using Microsoft.AspNetCore.Http.Features;
using Microsoft.AspNetCore.Protocols;
@ -391,12 +392,6 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http
protected abstract bool TryParseRequest(ReadResult result, out bool endConnection);
protected abstract void CreateHttpContext();
protected abstract void DisposeHttpContext();
protected abstract Task InvokeApplicationAsync();
private void CancelRequestAbortedToken()
{
try
@ -440,7 +435,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http
HttpRequestHeaders.Append(name, valueString);
}
public async Task ProcessRequestsAsync()
public async Task ProcessRequestsAsync<TContext>(IHttpApplication<TContext> application)
{
try
{
@ -474,14 +469,15 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http
InitializeStreams(messageBody);
CreateHttpContext();
var httpContext = application.CreateContext(this);
try
{
try
{
KestrelEventSource.Log.RequestStart(this);
await InvokeApplicationAsync();
await application.ProcessRequestAsync(httpContext);
if (_requestAborted == 0)
{
@ -563,7 +559,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http
}
finally
{
DisposeHttpContext();
application.DisposeContext(httpContext, _applicationException);
// StopStreams should be called before the end of the "if (!_requestProcessingStopping)" block
// to ensure InitializeStreams has been called.

View File

@ -6,6 +6,7 @@ using System.Collections.Concurrent;
using System.IO.Pipelines;
using System.Text;
using System.Threading.Tasks;
using Microsoft.AspNetCore.Builder;
using Microsoft.AspNetCore.Hosting.Server;
using Microsoft.AspNetCore.Http.Features;
using Microsoft.AspNetCore.Protocols;
@ -16,7 +17,7 @@ using Microsoft.Extensions.Logging;
namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http2
{
public class Http2Connection : ITimeoutControl, IHttp2StreamLifetimeHandler, IHttpHeadersHandler
public class Http2Connection : ITimeoutControl, IHttp2StreamLifetimeHandler, IHttpHeadersHandler, IRequestProcessor
{
private enum RequestHeaderParsingState
{
@ -93,13 +94,13 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http2
_frameWriter.Abort(ex);
}
public void Stop()
public void StopProcessingNextRequest()
{
_stopping = true;
Input.CancelPendingRead();
}
public async Task ProcessAsync<TContext>(IHttpApplication<TContext> application)
public async Task ProcessRequestsAsync<TContext>(IHttpApplication<TContext> application)
{
Exception error = null;
var errorCode = Http2ErrorCode.NO_ERROR;
@ -391,7 +392,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http2
else
{
// Start a new stream
_currentHeadersStream = new Http2Stream<TContext>(application, new Http2StreamContext
_currentHeadersStream = new Http2Stream(new Http2StreamContext
{
ConnectionId = ConnectionId,
StreamId = _incomingFrame.StreamId,
@ -412,7 +413,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http2
_currentHeadersStream.Reset();
var endHeaders = (_incomingFrame.HeadersFlags & Http2HeadersFrameFlags.END_HEADERS) == Http2HeadersFrameFlags.END_HEADERS;
await DecodeHeadersAsync(endHeaders, _incomingFrame.HeadersPayload);
await DecodeHeadersAsync(application, endHeaders, _incomingFrame.HeadersPayload);
}
}
@ -541,7 +542,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http2
throw new Http2ConnectionErrorException(CoreStrings.FormatHttp2ErrorStreamIdNotZero(_incomingFrame.Type), Http2ErrorCode.PROTOCOL_ERROR);
}
Stop();
StopProcessingNextRequest();
return Task.CompletedTask;
}
@ -602,7 +603,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http2
}
else
{
return DecodeHeadersAsync(endHeaders, _incomingFrame.Payload);
return DecodeHeadersAsync(application, endHeaders, _incomingFrame.Payload);
}
}
@ -616,7 +617,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http2
return Task.CompletedTask;
}
private Task DecodeHeadersAsync(bool endHeaders, Span<byte> payload)
private Task DecodeHeadersAsync<TContext>(IHttpApplication<TContext> application, bool endHeaders, Span<byte> payload)
{
try
{
@ -624,7 +625,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http2
if (endHeaders)
{
StartStream();
StartStream(application);
ResetRequestHeaderParsingState();
}
}
@ -652,7 +653,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http2
return Task.CompletedTask;
}
private void StartStream()
private void StartStream<TContext>(IHttpApplication<TContext> application)
{
if (!_isMethodConnect && (_parsedPseudoHeaderFields & _mandatoryRequestPseudoHeaderFields) != _mandatoryRequestPseudoHeaderFields)
{
@ -663,7 +664,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http2
}
_streams[_incomingFrame.StreamId] = _currentHeadersStream;
_ = _currentHeadersStream.ProcessRequestsAsync();
_ = _currentHeadersStream.ProcessRequestsAsync(application);
}
private void ResetRequestHeaderParsingState()

View File

@ -10,7 +10,7 @@ using Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Infrastructure;
namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http2
{
public abstract partial class Http2Stream : HttpProtocol
public partial class Http2Stream : HttpProtocol
{
private readonly Http2StreamContext _context;

View File

@ -1,27 +0,0 @@
// 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.Hosting.Server;
namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http2
{
public class Http2Stream<TContext> : Http2Stream
{
private readonly IHttpApplication<TContext> _application;
private TContext _httpContext;
public Http2Stream(IHttpApplication<TContext> application, Http2StreamContext context)
: base(context)
{
_application = application;
}
protected override void CreateHttpContext() => _httpContext = _application.CreateContext(this);
protected override void DisposeHttpContext() => _application.DisposeContext(_httpContext, _applicationException);
protected override Task InvokeApplicationAsync() => _application.ProcessRequestAsync(_httpContext);
}
}

View File

@ -21,30 +21,29 @@ using Microsoft.Extensions.Logging;
namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal
{
public class HttpConnection : ITimeoutControl, IConnectionTimeoutFeature
public class HttpConnection : ITimeoutControl, IConnectionTimeoutFeature, IRequestProcessor
{
private const int Http2ConnectionNotStarted = 0;
private const int Http2ConnectionStarted = 1;
private const int Http2ConnectionClosed = 2;
private readonly HttpConnectionContext _context;
private IList<IAdaptedConnection> _adaptedConnections;
private readonly TaskCompletionSource<object> _socketClosedTcs = new TaskCompletionSource<object>(TaskCreationOptions.RunContinuationsAsynchronously);
private IList<IAdaptedConnection> _adaptedConnections;
private IPipeConnection _adaptedTransport;
private readonly object _protocolSelectionLock = new object();
private IRequestProcessor _requestProcessor;
private Http1Connection _http1Connection;
private Http2Connection _http2Connection;
private volatile int _http2ConnectionState;
private long _lastTimestamp;
private long _timeoutTimestamp = long.MaxValue;
private TimeoutAction _timeoutAction;
private object _readTimingLock = new object();
private readonly object _readTimingLock = new object();
private bool _readTimingEnabled;
private bool _readTimingPauseRequested;
private long _readTimingElapsedTicks;
private long _readTimingBytesRead;
private object _writeTimingLock = new object();
private readonly object _writeTimingLock = new object();
private int _writeTimingWrites;
private long _writeTimingTimeoutTimestamp;
@ -53,6 +52,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal
public HttpConnection(HttpConnectionContext context)
{
_context = context;
_requestProcessor = this;
}
// For testing
@ -102,35 +102,29 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal
AdaptedPipeline adaptedPipeline = null;
var adaptedPipelineTask = Task.CompletedTask;
var transport = _context.Transport;
// _adaptedTransport must be set prior to adding the connection to the manager in order
// to allow the connection to be aported prior to protocol selection.
_adaptedTransport = _context.Transport;
var application = _context.Application;
if (_context.ConnectionAdapters.Count > 0)
{
adaptedPipeline = new AdaptedPipeline(transport,
adaptedPipeline = new AdaptedPipeline(_adaptedTransport,
application,
new Pipe(AdaptedInputPipeOptions),
new Pipe(AdaptedOutputPipeOptions));
transport = adaptedPipeline;
_adaptedTransport = adaptedPipeline;
}
// _http1Connection must be initialized before adding the connection to the connection manager
CreateHttp1Connection(httpApplication, transport, application);
// _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.
CreateHttp2Connection(httpApplication, transport, application);
// Do this before the first await so we don't yield control to the transport until we've
// added the connection to the connection manager
_context.ServiceContext.ConnectionManager.AddConnection(_context.HttpConnectionId, this);
_lastTimestamp = _context.ServiceContext.SystemClock.UtcNow.Ticks;
_http1Connection.ConnectionFeatures.Set<IConnectionTimeoutFeature>(this);
_http2Connection.ConnectionFeatures.Set<IConnectionTimeoutFeature>(this);
_context.ConnectionFeatures.Set<IConnectionTimeoutFeature>(this);
if (adaptedPipeline != null)
{
@ -139,21 +133,41 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal
adaptedPipelineTask = adaptedPipeline.RunAsync(stream);
}
var protocol = SelectProtocol();
IRequestProcessor requestProcessor = null;
if (protocol == HttpProtocols.None)
lock (_protocolSelectionLock)
{
Abort(ex: null);
// Ensure that the connection hasn't already been stopped.
if (_requestProcessor == this)
{
switch (SelectProtocol())
{
case HttpProtocols.Http1:
// _http1Connection must be initialized before adding the connection to the connection manager
requestProcessor = _http1Connection = CreateHttp1Connection(_adaptedTransport, application);
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 = CreateHttp2Connection(_adaptedTransport, application);
break;
case HttpProtocols.None:
// An error was already logged in SelectProtocol(), but we should close the connection.
Abort(ex: null);
break;
default:
// SelectProtocol() only returns Http1, Http2 or None.
throw new NotSupportedException($"{nameof(SelectProtocol)} returned something other than Http1, Http2 or None.");
}
_requestProcessor = requestProcessor;
}
}
// One of these has to run even if no protocol was selected so the abort propagates and everything completes properly
if (protocol == HttpProtocols.Http2 && Interlocked.CompareExchange(ref _http2ConnectionState, Http2ConnectionStarted, Http2ConnectionNotStarted) == Http2ConnectionNotStarted)
if (requestProcessor != null)
{
await _http2Connection.ProcessAsync(httpApplication);
}
else
{
await _http1Connection.ProcessRequestsAsync();
await requestProcessor.ProcessRequestsAsync(httpApplication);
}
await adaptedPipelineTask;
@ -161,14 +175,14 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal
}
catch (Exception ex)
{
Log.LogError(0, ex, $"Unexpected exception in {nameof(HttpConnection)}.{nameof(ProcessRequestsAsync)}.");
Log.LogCritical(0, ex, $"Unexpected exception in {nameof(HttpConnection)}.{nameof(ProcessRequestsAsync)}.");
}
finally
{
_context.ServiceContext.ConnectionManager.RemoveConnection(_context.HttpConnectionId);
DisposeAdaptedConnections();
if (_http1Connection.IsUpgraded)
if (_http1Connection?.IsUpgraded == true)
{
_context.ServiceContext.ConnectionManager.UpgradedConnectionCount.ReleaseOne();
}
@ -177,9 +191,15 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal
}
}
internal void CreateHttp1Connection<TContext>(IHttpApplication<TContext> httpApplication, IPipeConnection transport, IPipeConnection application)
// For testing only
internal void Initialize(IPipeConnection transport, IPipeConnection application)
{
_http1Connection = new Http1Connection<TContext>(httpApplication, new Http1ConnectionContext
_requestProcessor = _http1Connection = CreateHttp1Connection(transport, application);
}
private Http1Connection CreateHttp1Connection(IPipeConnection transport, IPipeConnection application)
{
return new Http1Connection(new Http1ConnectionContext
{
ConnectionId = _context.ConnectionId,
ConnectionFeatures = _context.ConnectionFeatures,
@ -193,9 +213,9 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal
});
}
internal void CreateHttp2Connection<TContext>(IHttpApplication<TContext> httpApplication, IPipeConnection transport, IPipeConnection application)
private Http2Connection CreateHttp2Connection(IPipeConnection transport, IPipeConnection application)
{
_http2Connection = new Http2Connection(new Http2ConnectionContext
return new Http2Connection(new Http2ConnectionContext
{
ConnectionId = _context.ConnectionId,
ServiceContext = _context.ServiceContext,
@ -217,16 +237,10 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal
public Task StopProcessingNextRequestAsync()
{
Debug.Assert(_http1Connection != null, $"{nameof(_http1Connection)} is null");
Debug.Assert(_http2Connection != null, $"{nameof(_http2Connection)} is null");
if (Interlocked.Exchange(ref _http2ConnectionState, Http2ConnectionClosed) == Http2ConnectionStarted)
lock (_protocolSelectionLock)
{
_http2Connection.Stop();
}
else
{
_http1Connection.StopProcessingNextRequest();
_requestProcessor?.StopProcessingNextRequest();
_requestProcessor = null;
}
return _lifetimeTask;
@ -234,17 +248,10 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal
public void Abort(Exception ex)
{
Debug.Assert(_http1Connection != null, $"{nameof(_http1Connection)} is null");
Debug.Assert(_http2Connection != null, $"{nameof(_http2Connection)} is null");
// Abort the connection (if not already aborted)
if (Interlocked.Exchange(ref _http2ConnectionState, Http2ConnectionClosed) == Http2ConnectionStarted)
lock (_protocolSelectionLock)
{
_http2Connection.Abort(ex);
}
else
{
_http1Connection.Abort(ex);
_requestProcessor?.Abort(ex);
_requestProcessor = null;
}
}
@ -255,28 +262,8 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal
return _lifetimeTask;
}
public void SendTimeoutResponse()
{
Debug.Assert(_http1Connection != null, $"{nameof(_http1Connection)} is null");
Debug.Assert(_http2Connection != null, $"{nameof(_http2Connection)} is null");
RequestTimedOut = true;
_http1Connection.SendTimeoutResponse();
}
public void StopProcessingNextRequest()
{
Debug.Assert(_http1Connection != null, $"{nameof(_http1Connection)} is null");
Debug.Assert(_http2Connection != null, $"{nameof(_http2Connection)} is null");
_http1Connection.StopProcessingNextRequest();
}
private async Task<Stream> ApplyConnectionAdaptersAsync()
{
Debug.Assert(_http1Connection != null, $"{nameof(_http1Connection)} is null");
Debug.Assert(_http2Connection != null, $"{nameof(_http2Connection)} is null");
var connectionAdapters = _context.ConnectionAdapters;
var stream = new RawStream(_context.Transport.Input, _context.Transport.Output);
var adapterContext = new ConnectionAdapterContext(_context.ConnectionFeatures, stream);
@ -348,14 +335,16 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal
public void Tick(DateTimeOffset now)
{
Debug.Assert(_http1Connection != null, $"{nameof(_http1Connection)} is null");
Debug.Assert(_http2Connection != null, $"{nameof(_http2Connection)} is null");
var timestamp = now.Ticks;
CheckForTimeout(timestamp);
CheckForReadDataRateTimeout(timestamp);
CheckForWriteDataRateTimeout(timestamp);
// HTTP/2 rate timeouts are not yet supported.
if (_http1Connection != null)
{
CheckForReadDataRateTimeout(timestamp);
CheckForWriteDataRateTimeout(timestamp);
}
Interlocked.Exchange(ref _lastTimestamp, timestamp);
}
@ -372,12 +361,19 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal
switch (_timeoutAction)
{
case TimeoutAction.StopProcessingNextRequest:
StopProcessingNextRequest();
// Http/2 keep-alive timeouts are not yet supported.
_http1Connection?.StopProcessingNextRequest();
break;
case TimeoutAction.SendTimeoutResponse:
SendTimeoutResponse();
// HTTP/2 timeout responses are not yet supported.
if (_http1Connection != null)
{
RequestTimedOut = true;
_http1Connection.SendTimeoutResponse();
}
break;
case TimeoutAction.AbortConnection:
// This is actually supported with HTTP/2!
Abort(new TimeoutException());
break;
}
@ -387,6 +383,8 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal
private void CheckForReadDataRateTimeout(long timestamp)
{
Debug.Assert(_http1Connection != null);
// The only time when both a timeout is set and the read data rate could be enforced is
// when draining the request body. Since there's already a (short) timeout set for draining,
// it's safe to not check the data rate at this point.
@ -412,7 +410,8 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal
if (rate < minRequestBodyDataRate.BytesPerSecond && !Debugger.IsAttached)
{
Log.RequestBodyMininumDataRateNotSatisfied(_context.ConnectionId, _http1Connection.TraceIdentifier, minRequestBodyDataRate.BytesPerSecond);
SendTimeoutResponse();
RequestTimedOut = true;
_http1Connection.SendTimeoutResponse();
}
}
@ -430,6 +429,8 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal
private void CheckForWriteDataRateTimeout(long timestamp)
{
Debug.Assert(_http1Connection != null);
lock (_writeTimingLock)
{
if (_writeTimingWrites > 0 && timestamp > _writeTimingTimeoutTimestamp && !Debugger.IsAttached)
@ -510,6 +511,8 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal
public void StartTimingWrite(long size)
{
Debug.Assert(_http1Connection != null);
lock (_writeTimingLock)
{
var minResponseDataRate = _http1Connection.MinResponseDataRate;
@ -563,5 +566,33 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal
ResetTimeout(timeSpan.Ticks, TimeoutAction.AbortConnection);
}
private void CloseUninitializedConnection()
{
Debug.Assert(_adaptedTransport != null);
// CancelPendingRead signals the transport directly to close the connection
// without any potential interference from connection adapters.
_context.Application.Input.CancelPendingRead();
_adaptedTransport.Input.Complete();
_adaptedTransport.Output.Complete();
}
// These IStoppableConnection methods only get called if the server shuts down during initialization.
Task IRequestProcessor.ProcessRequestsAsync<TContext>(IHttpApplication<TContext> application)
{
throw new NotSupportedException();
}
void IRequestProcessor.StopProcessingNextRequest()
{
CloseUninitializedConnection();
}
void IRequestProcessor.Abort(Exception ex)
{
CloseUninitializedConnection();
}
}
}

View File

@ -0,0 +1,16 @@
// 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.Threading.Tasks;
using Microsoft.AspNetCore.Hosting.Server;
namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal
{
public interface IRequestProcessor
{
Task ProcessRequestsAsync<TContext>(IHttpApplication<TContext> application);
void StopProcessingNextRequest();
void Abort(Exception ex);
}
}

View File

@ -11,7 +11,6 @@ using System.Linq;
using System.Text;
using System.Threading;
using System.Threading.Tasks;
using Microsoft.AspNetCore.Hosting.Server;
using Microsoft.AspNetCore.Http;
using Microsoft.AspNetCore.Http.Features;
using Microsoft.AspNetCore.Server.Kestrel.Core.Features;
@ -30,7 +29,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Tests
{
private readonly IPipeConnection _transport;
private readonly IPipeConnection _application;
private readonly TestHttp1Connection<object> _http1Connection;
private readonly TestHttp1Connection _http1Connection;
private readonly ServiceContext _serviceContext;
private readonly Http1ConnectionContext _http1ConnectionContext;
private readonly BufferPool _pipelineFactory;
@ -38,19 +37,6 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Tests
private ReadCursor _examined;
private Mock<ITimeoutControl> _timeoutControl;
private class TestHttp1Connection<TContext> : Http1Connection<TContext>
{
public TestHttp1Connection(IHttpApplication<TContext> application, Http1ConnectionContext context)
: base(application, context)
{
}
public Task ProduceEndAsync()
{
return ProduceEnd();
}
}
public Http1ConnectionTests()
{
_pipelineFactory = new MemoryPool();
@ -71,7 +57,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Tests
Transport = pair.Transport
};
_http1Connection = new TestHttp1Connection<object>(application: null, context: _http1ConnectionContext);
_http1Connection = new TestHttp1Connection(_http1ConnectionContext);
_http1Connection.Reset();
}
@ -509,7 +495,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Tests
[Fact]
public void ProcessRequestsAsyncEnablesKeepAliveTimeout()
{
var requestProcessingTask = _http1Connection.ProcessRequestsAsync();
var requestProcessingTask = _http1Connection.ProcessRequestsAsync<object>(null);
var expectedKeepAliveTimeout = _serviceContext.ServerOptions.Limits.KeepAliveTimeout.Ticks;
_timeoutControl.Verify(cc => cc.SetTimeout(expectedKeepAliveTimeout, TimeoutAction.StopProcessingNextRequest));
@ -594,7 +580,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Tests
[Fact]
public async Task RequestProcessingTaskIsUnwrapped()
{
var requestProcessingTask = _http1Connection.ProcessRequestsAsync();
var requestProcessingTask = _http1Connection.ProcessRequestsAsync<object>(null);
var data = Encoding.ASCII.GetBytes("GET / HTTP/1.1\r\nHost:\r\n\r\n");
await _application.Output.WriteAsync(data);
@ -723,7 +709,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Tests
var headers0 = MakeHeaders(header0Count);
var headers1 = MakeHeaders(header1Count, header0Count);
var requestProcessingTask = _http1Connection.ProcessRequestsAsync();
var requestProcessingTask = _http1Connection.ProcessRequestsAsync<object>(null);
await _application.Output.WriteAsync(Encoding.ASCII.GetBytes("GET / HTTP/1.0\r\n"));
await WaitForCondition(TimeSpan.FromSeconds(1), () => _http1Connection.RequestHeaders != null);
@ -757,7 +743,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Tests
var headers0 = MakeHeaders(header0Count);
var headers1 = MakeHeaders(header1Count, header0Count);
var requestProcessingTask = _http1Connection.ProcessRequestsAsync();
var requestProcessingTask = _http1Connection.ProcessRequestsAsync<object>(null);
await _application.Output.WriteAsync(Encoding.ASCII.GetBytes("GET / HTTP/1.0\r\n"));
await WaitForCondition(TimeSpan.FromSeconds(1), () => _http1Connection.RequestHeaders != null);

View File

@ -1987,7 +1987,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Tests
private async Task InitializeConnectionAsync(RequestDelegate application)
{
_connectionTask = _connection.ProcessAsync(new DummyApplication(application));
_connectionTask = _connection.ProcessRequestsAsync(new DummyApplication(application));
await SendPreambleAsync().ConfigureAwait(false);
await SendSettingsAsync();

View File

@ -56,8 +56,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Tests
var mockDebugger = new Mock<IDebugger>();
mockDebugger.SetupGet(g => g.IsAttached).Returns(true);
_httpConnection.Debugger = mockDebugger.Object;
_httpConnection.CreateHttp1Connection(new DummyApplication(), _httpConnectionContext.Transport, _httpConnectionContext.Application);
_httpConnection.CreateHttp2Connection(new DummyApplication(), _httpConnectionContext.Transport, _httpConnectionContext.Application);
_httpConnection.Initialize(_httpConnectionContext.Transport, _httpConnectionContext.Application);
var now = DateTimeOffset.Now;
_httpConnection.Tick(now);
@ -104,8 +103,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Tests
_httpConnectionContext.ServiceContext.Log = logger;
_httpConnection.CreateHttp1Connection(new DummyApplication(), _httpConnectionContext.Transport, _httpConnectionContext.Application);
_httpConnection.CreateHttp2Connection(new DummyApplication(), _httpConnectionContext.Transport, _httpConnectionContext.Application);
_httpConnection.Initialize(_httpConnectionContext.Transport, _httpConnectionContext.Application);
_httpConnection.Http1Connection.Reset();
// Initialize timestamp
@ -132,8 +130,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Tests
var mockLogger = new Mock<IKestrelTrace>();
_httpConnectionContext.ServiceContext.Log = mockLogger.Object;
_httpConnection.CreateHttp1Connection(new DummyApplication(), _httpConnectionContext.Transport, _httpConnectionContext.Application);
_httpConnection.CreateHttp2Connection(new DummyApplication(), _httpConnectionContext.Transport, _httpConnectionContext.Application);
_httpConnection.Initialize(_httpConnectionContext.Transport, _httpConnectionContext.Application);
_httpConnection.Http1Connection.Reset();
// Initialize timestamp
@ -175,8 +172,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Tests
var mockLogger = new Mock<IKestrelTrace>();
_httpConnectionContext.ServiceContext.Log = mockLogger.Object;
_httpConnection.CreateHttp1Connection(new DummyApplication(), _httpConnectionContext.Transport, _httpConnectionContext.Application);
_httpConnection.CreateHttp2Connection(new DummyApplication(), _httpConnectionContext.Transport, _httpConnectionContext.Application);
_httpConnection.Initialize(_httpConnectionContext.Transport, _httpConnectionContext.Application);
_httpConnection.Http1Connection.Reset();
// Initialize timestamp
@ -253,8 +249,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Tests
var mockLogger = new Mock<IKestrelTrace>();
_httpConnectionContext.ServiceContext.Log = mockLogger.Object;
_httpConnection.CreateHttp1Connection(new DummyApplication(), _httpConnectionContext.Transport, _httpConnectionContext.Application);
_httpConnection.CreateHttp2Connection(new DummyApplication(), _httpConnectionContext.Transport, _httpConnectionContext.Application);
_httpConnection.Initialize(_httpConnectionContext.Transport, _httpConnectionContext.Application);
_httpConnection.Http1Connection.Reset();
// Initialize timestamp
@ -322,8 +317,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Tests
var mockLogger = new Mock<IKestrelTrace>();
_httpConnectionContext.ServiceContext.Log = mockLogger.Object;
_httpConnection.CreateHttp1Connection(new DummyApplication(), _httpConnectionContext.Transport, _httpConnectionContext.Application);
_httpConnection.CreateHttp2Connection(new DummyApplication(), _httpConnectionContext.Transport, _httpConnectionContext.Application);
_httpConnection.Initialize(_httpConnectionContext.Transport, _httpConnectionContext.Application);
_httpConnection.Http1Connection.Reset();
// Initialize timestamp
@ -385,8 +379,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Tests
var mockLogger = new Mock<IKestrelTrace>();
_httpConnectionContext.ServiceContext.Log = mockLogger.Object;
_httpConnection.CreateHttp1Connection(new DummyApplication(), _httpConnectionContext.Transport, _httpConnectionContext.Application);
_httpConnection.CreateHttp2Connection(new DummyApplication(), _httpConnectionContext.Transport, _httpConnectionContext.Application);
_httpConnection.Initialize(_httpConnectionContext.Transport, _httpConnectionContext.Application);
_httpConnection.Http1Connection.Reset();
var startTime = systemClock.UtcNow;
@ -427,8 +420,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Tests
var mockLogger = new Mock<IKestrelTrace>();
_httpConnectionContext.ServiceContext.Log = mockLogger.Object;
_httpConnection.CreateHttp1Connection(new DummyApplication(), _httpConnectionContext.Transport, _httpConnectionContext.Application);
_httpConnection.CreateHttp2Connection(new DummyApplication(), _httpConnectionContext.Transport, _httpConnectionContext.Application);
_httpConnection.Initialize(_httpConnectionContext.Transport, _httpConnectionContext.Application);
_httpConnection.Http1Connection.Reset();
_httpConnection.Http1Connection.RequestAborted.Register(() =>
{
@ -462,8 +454,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Tests
var mockLogger = new Mock<IKestrelTrace>();
_httpConnectionContext.ServiceContext.Log = mockLogger.Object;
_httpConnection.CreateHttp1Connection(new DummyApplication(), _httpConnectionContext.Transport, _httpConnectionContext.Application);
_httpConnection.CreateHttp2Connection(new DummyApplication(), _httpConnectionContext.Transport, _httpConnectionContext.Application);
_httpConnection.Initialize(_httpConnectionContext.Transport, _httpConnectionContext.Application);
_httpConnection.Http1Connection.Reset();
_httpConnection.Http1Connection.RequestAborted.Register(() =>
{
@ -505,8 +496,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Tests
var mockLogger = new Mock<IKestrelTrace>();
_httpConnectionContext.ServiceContext.Log = mockLogger.Object;
_httpConnection.CreateHttp1Connection(new DummyApplication(), _httpConnectionContext.Transport, _httpConnectionContext.Application);
_httpConnection.CreateHttp2Connection(new DummyApplication(), _httpConnectionContext.Transport, _httpConnectionContext.Application);
_httpConnection.Initialize(_httpConnectionContext.Transport, _httpConnectionContext.Application);
_httpConnection.Http1Connection.Reset();
_httpConnection.Http1Connection.RequestAborted.Register(() =>
{

View File

@ -33,7 +33,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Tests
TimeoutControl = null
};
var http1Connection = new Http1Connection<object>(application: null, context: http1ConnectionContext);
var http1Connection = new Http1Connection(http1ConnectionContext);
http1Connection.Reset();

View File

@ -34,7 +34,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Tests
TimeoutControl = Mock.Of<ITimeoutControl>()
};
Http1Connection = new Http1Connection<object>(null, Http1ConnectionContext);
Http1Connection = new Http1Connection(Http1ConnectionContext);
Http1Connection.HttpResponseControl = Mock.Of<IHttpResponseControl>();
}

View File

@ -708,7 +708,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Transport.Libuv.Tests
var socket = new MockSocket(_mockLibuv, _libuvThread.Loop.ThreadId, transportContext.Log);
var consumer = new LibuvOutputConsumer(pair.Application.Input, _libuvThread, socket, "0", transportContext.Log);
var http1Connection = new Http1Connection<object>(null, new Http1ConnectionContext
var http1Connection = new Http1Connection(new Http1ConnectionContext
{
ServiceContext = serviceContext,
ConnectionFeatures = new FeatureCollection(),

View File

@ -2,16 +2,14 @@
// 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.Hosting.Server;
using Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http;
using Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Infrastructure;
namespace Microsoft.AspNetCore.Testing
{
public class TestHttp1Connection<TContext> : Http1Connection<TContext>
public class TestHttp1Connection : Http1Connection
{
public TestHttp1Connection(IHttpApplication<TContext> application, Http1ConnectionContext context)
: base(application, context)
public TestHttp1Connection(Http1ConnectionContext context)
: base(context)
{
}