Simplify connection lifetime control flow (#1776)

* Also make IAdaptedConnection disposable
This commit is contained in:
Stephen Halter 2017-05-05 16:11:01 -07:00 committed by GitHub
parent 28b479c99a
commit 6e2fdda162
23 changed files with 229 additions and 236 deletions

View File

@ -5,7 +5,9 @@ using System;
using System.IO; using System.IO;
using System.Threading.Tasks; using System.Threading.Tasks;
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.Internal.System.IO.Pipelines; using Microsoft.AspNetCore.Server.Kestrel.Internal.System.IO.Pipelines;
using Microsoft.Extensions.Logging;
namespace Microsoft.AspNetCore.Server.Kestrel.Core.Adapter.Internal namespace Microsoft.AspNetCore.Server.Kestrel.Core.Adapter.Internal
{ {
@ -15,16 +17,18 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Adapter.Internal
private readonly Stream _filteredStream; private readonly Stream _filteredStream;
private readonly StreamSocketOutput _output; private readonly StreamSocketOutput _output;
private readonly IKestrelTrace _trace;
public AdaptedPipeline( public AdaptedPipeline(
Stream filteredStream, Stream filteredStream,
IPipe inputPipe, IPipe inputPipe,
IPipe outputPipe) IPipe outputPipe,
IKestrelTrace trace)
{ {
Input = inputPipe; Input = inputPipe;
_output = new StreamSocketOutput(filteredStream, outputPipe); _output = new StreamSocketOutput(filteredStream, outputPipe);
_filteredStream = filteredStream; _filteredStream = filteredStream;
_trace = trace;
} }
public IPipe Input { get; } public IPipe Input { get; }
@ -33,14 +37,22 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Adapter.Internal
public async Task RunAsync() public async Task RunAsync()
{ {
var inputTask = ReadInputAsync(); try
var outputTask = _output.WriteOutputAsync(); {
var inputTask = ReadInputAsync();
var outputTask = _output.WriteOutputAsync();
await inputTask; await inputTask;
_output.Dispose(); _output.Dispose();
await outputTask; await outputTask;
}
catch (Exception ex)
{
// adaptedPipeline.RunAsync() shouldn't throw, unless filtered stream's WriteAsync throws.
_trace.LogError(0, ex, $"{nameof(AdaptedPipeline)}.{nameof(RunAsync)}");
}
} }
private async Task ReadInputAsync() private async Task ReadInputAsync()

View File

@ -1,12 +1,13 @@
// Copyright (c) .NET Foundation. All rights reserved. // 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. // Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
using System;
using System.IO; using System.IO;
using Microsoft.AspNetCore.Http.Features; using Microsoft.AspNetCore.Http.Features;
namespace Microsoft.AspNetCore.Server.Kestrel.Core.Adapter.Internal namespace Microsoft.AspNetCore.Server.Kestrel.Core.Adapter.Internal
{ {
public interface IAdaptedConnection public interface IAdaptedConnection : IDisposable
{ {
Stream ConnectionStream { get; } Stream ConnectionStream { get; }

View File

@ -5,10 +5,9 @@ using System;
using System.IO; using System.IO;
using System.Threading.Tasks; using System.Threading.Tasks;
using Microsoft.AspNetCore.Http.Features; using Microsoft.AspNetCore.Http.Features;
using Microsoft.AspNetCore.Server.Kestrel.Core.Adapter.Internal;
using Microsoft.Extensions.Logging; using Microsoft.Extensions.Logging;
namespace Microsoft.AspNetCore.Server.Kestrel.Core.Adapter namespace Microsoft.AspNetCore.Server.Kestrel.Core.Adapter.Internal
{ {
public class LoggingConnectionAdapter : IConnectionAdapter public class LoggingConnectionAdapter : IConnectionAdapter
{ {
@ -44,6 +43,10 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Adapter
public void PrepareRequest(IFeatureCollection requestFeatures) public void PrepareRequest(IFeatureCollection requestFeatures)
{ {
} }
public void Dispose()
{
}
} }
} }
} }

View File

@ -209,5 +209,11 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Adapter.Internal
}, tcs, cancellationToken); }, tcs, cancellationToken);
return tcs.Task; return tcs.Task;
} }
protected override void Dispose(bool disposing)
{
// _output is disposed by ConnectionLifetimeControl
_input.Complete();
}
} }
} }

View File

@ -2,7 +2,7 @@
// 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 Microsoft.AspNetCore.Server.Kestrel.Core; using Microsoft.AspNetCore.Server.Kestrel.Core;
using Microsoft.AspNetCore.Server.Kestrel.Core.Adapter; using Microsoft.AspNetCore.Server.Kestrel.Core.Adapter.Internal;
using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Logging; using Microsoft.Extensions.Logging;

View File

@ -62,7 +62,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal
KestrelEventSource.Log.ConnectionStart(connection, connectionInfo); KestrelEventSource.Log.ConnectionStart(connection, connectionInfo);
// Since data cannot be added to the inputPipe by the transport until OnConnection returns, // Since data cannot be added to the inputPipe by the transport until OnConnection returns,
// Frame.RequestProcessingAsync is guaranteed to unblock the transport thread before calling // Frame.ProcessRequestsAsync is guaranteed to unblock the transport thread before calling
// application code. // application code.
connection.StartRequestProcessing(); connection.StartRequestProcessing();

View File

@ -21,23 +21,20 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal
private readonly FrameConnectionContext _context; private readonly FrameConnectionContext _context;
private readonly Frame _frame; private readonly Frame _frame;
private readonly List<IConnectionAdapter> _connectionAdapters; private readonly List<IConnectionAdapter> _connectionAdapters;
private readonly TaskCompletionSource<bool> _frameStartedTcs = new TaskCompletionSource<bool>(TaskCreationOptions.RunContinuationsAsynchronously);
private readonly TaskCompletionSource<object> _socketClosedTcs = new TaskCompletionSource<object>(TaskCreationOptions.RunContinuationsAsynchronously); private readonly TaskCompletionSource<object> _socketClosedTcs = new TaskCompletionSource<object>(TaskCreationOptions.RunContinuationsAsynchronously);
private long _lastTimestamp; private long _lastTimestamp;
private long _timeoutTimestamp = long.MaxValue; private long _timeoutTimestamp = long.MaxValue;
private TimeoutAction _timeoutAction; private TimeoutAction _timeoutAction;
private AdaptedPipeline _adaptedPipeline; private Task _lifetimeTask;
private Stream _filteredStream; private Stream _filteredStream;
private Task _adaptedPipelineTask;
public FrameConnection(FrameConnectionContext context) public FrameConnection(FrameConnectionContext context)
{ {
_context = context; _context = context;
_frame = context.Frame; _frame = context.Frame;
_connectionAdapters = context.ConnectionAdapters; _connectionAdapters = context.ConnectionAdapters;
context.ServiceContext.ConnectionManager.AddConnection(context.FrameConnectionId, this);
} }
public string ConnectionId => _context.ConnectionId; public string ConnectionId => _context.ConnectionId;
@ -67,51 +64,82 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal
public void StartRequestProcessing() public void StartRequestProcessing()
{ {
_frame.Input = _context.Input.Reader; _lifetimeTask = ProcessRequestsAsync();
_frame.Output = _context.OutputProducer; }
_frame.TimeoutControl = this;
if (_connectionAdapters.Count == 0) private async Task ProcessRequestsAsync()
{
RawStream rawStream = null;
try
{ {
StartFrame(); Task adaptedPipelineTask = Task.CompletedTask;
}
else if (_connectionAdapters.Count == 0)
{
// Ensure that IConnectionAdapter.OnConnectionAsync does not run on the transport thread.
_context.ServiceContext.ThreadPool.UnsafeRun(state =>
{ {
// ApplyConnectionAdaptersAsync should never throw. If it succeeds, it will call _frame.Start(). _frame.Input = _context.Input.Reader;
// Otherwise, it will close the connection. _frame.Output = _context.OutputProducer;
var ignore = ((FrameConnection)state).ApplyConnectionAdaptersAsync(); }
}, this); else
{
rawStream = new RawStream(_context.Input.Reader, _context.OutputProducer);
try
{
var adaptedPipeline = await ApplyConnectionAdaptersAsync(rawStream);
_frame.Input = adaptedPipeline.Input.Reader;
_frame.Output = adaptedPipeline.Output;
adaptedPipelineTask = adaptedPipeline.RunAsync();
}
catch (Exception ex)
{
Log.LogError(0, ex, $"Uncaught exception from the {nameof(IConnectionAdapter.OnConnectionAsync)} method of an {nameof(IConnectionAdapter)}.");
// Since Frame.ProcessRequestsAsync() isn't called, we have to close the socket here.
_context.OutputProducer.Dispose();
await _socketClosedTcs.Task;
return;
}
}
_frame.TimeoutControl = this;
_lastTimestamp = _context.ServiceContext.SystemClock.UtcNow.Ticks;
_context.ServiceContext.ConnectionManager.AddConnection(_context.FrameConnectionId, this);
await _frame.ProcessRequestsAsync();
await adaptedPipelineTask;
await _socketClosedTcs.Task;
}
catch (Exception ex)
{
Log.LogError(0, ex, $"Unexpected exception in {nameof(FrameConnection)}.{nameof(ProcessRequestsAsync)}.");
}
finally
{
_context.ServiceContext.ConnectionManager.RemoveConnection(_context.FrameConnectionId);
rawStream?.Dispose();
DisposeAdaptedConnections();
} }
} }
public async void OnConnectionClosed(Exception ex) public void OnConnectionClosed(Exception ex)
{ {
// Abort the connection (if it isn't already aborted) // Abort the connection (if it isn't already aborted)
_frame.Abort(ex); _frame.Abort(ex);
Log.ConnectionStop(ConnectionId); Log.ConnectionStop(ConnectionId);
KestrelEventSource.Log.ConnectionStop(this); KestrelEventSource.Log.ConnectionStop(this);
_socketClosedTcs.SetResult(null); _socketClosedTcs.TrySetResult(null);
// The connection is already in the "aborted" state by this point, but we want to track it
// until RequestProcessingAsync completes for graceful shutdown.
await StopAsync();
_context.ServiceContext.ConnectionManager.RemoveConnection(_context.FrameConnectionId);
} }
public async Task StopAsync() public Task StopAsync()
{ {
if (await _frameStartedTcs.Task) _frame.Stop();
{ return _lifetimeTask;
await _frame.StopAsync();
await (_adaptedPipelineTask ?? Task.CompletedTask);
}
await _socketClosedTcs.Task;
} }
public void Abort(Exception ex) public void Abort(Exception ex)
@ -122,7 +150,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal
public Task AbortAsync(Exception ex) public Task AbortAsync(Exception ex)
{ {
_frame.Abort(ex); _frame.Abort(ex);
return StopAsync(); return _lifetimeTask;
} }
public void Timeout() public void Timeout()
@ -130,76 +158,37 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal
_frame.SetBadRequestState(RequestRejectionReason.RequestTimeout); _frame.SetBadRequestState(RequestRejectionReason.RequestTimeout);
} }
private async Task ApplyConnectionAdaptersAsync() private async Task<AdaptedPipeline> ApplyConnectionAdaptersAsync(RawStream rawStream)
{ {
try var adapterContext = new ConnectionAdapterContext(rawStream);
{ var adaptedConnections = new IAdaptedConnection[_connectionAdapters.Count];
var rawSocketOutput = _frame.Output;
var rawStream = new RawStream(_frame.Input, rawSocketOutput);
var adapterContext = new ConnectionAdapterContext(rawStream);
var adaptedConnections = new IAdaptedConnection[_connectionAdapters.Count];
for (var i = 0; i < _connectionAdapters.Count; i++) for (var i = 0; i < _connectionAdapters.Count; i++)
{
var adaptedConnection = await _connectionAdapters[i].OnConnectionAsync(adapterContext);
adaptedConnections[i] = adaptedConnection;
adapterContext = new ConnectionAdapterContext(adaptedConnection.ConnectionStream);
}
_filteredStream = adapterContext.ConnectionStream;
_frame.AdaptedConnections = adaptedConnections;
return new AdaptedPipeline(_filteredStream,
PipeFactory.Create(AdaptedInputPipeOptions),
PipeFactory.Create(AdaptedOutputPipeOptions),
Log);
}
private void DisposeAdaptedConnections()
{
var adaptedConnections = _frame.AdaptedConnections;
if (adaptedConnections != null)
{
for (int i = adaptedConnections.Length - 1; i >= 0; i--)
{ {
var adaptedConnection = await _connectionAdapters[i].OnConnectionAsync(adapterContext); adaptedConnections[i].Dispose();
adaptedConnections[i] = adaptedConnection;
adapterContext = new ConnectionAdapterContext(adaptedConnection.ConnectionStream);
} }
if (adapterContext.ConnectionStream != rawStream)
{
_filteredStream = adapterContext.ConnectionStream;
_adaptedPipeline = new AdaptedPipeline(
adapterContext.ConnectionStream,
PipeFactory.Create(AdaptedInputPipeOptions),
PipeFactory.Create(AdaptedOutputPipeOptions));
_frame.Input = _adaptedPipeline.Input.Reader;
_frame.Output = _adaptedPipeline.Output;
_adaptedPipelineTask = RunAdaptedPipeline();
}
_frame.AdaptedConnections = adaptedConnections;
StartFrame();
} }
catch (Exception ex)
{
Log.LogError(0, ex, $"Uncaught exception from the {nameof(IConnectionAdapter.OnConnectionAsync)} method of an {nameof(IConnectionAdapter)}.");
_frameStartedTcs.SetResult(false);
CloseRawPipes();
}
}
private async Task RunAdaptedPipeline()
{
try
{
await _adaptedPipeline.RunAsync();
}
catch (Exception ex)
{
// adaptedPipeline.RunAsync() shouldn't throw.
Log.LogError(0, ex, $"{nameof(FrameConnection)}.{nameof(ApplyConnectionAdaptersAsync)}");
}
finally
{
CloseRawPipes();
}
}
private void CloseRawPipes()
{
_filteredStream?.Dispose();
_context.OutputProducer.Dispose();
_context.Input.Reader.Complete();
}
private void StartFrame()
{
_lastTimestamp = _context.ServiceContext.SystemClock.UtcNow.Ticks;
_frame.Start();
_frameStartedTcs.SetResult(true);
} }
public void Tick(DateTimeOffset now) public void Tick(DateTimeOffset now)
@ -216,7 +205,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal
Timeout(); Timeout();
} }
var ignore = StopAsync(); _frame.Stop();
} }
Interlocked.Exchange(ref _lastTimestamp, timestamp); Interlocked.Exchange(ref _lastTimestamp, timestamp);

View File

@ -54,7 +54,6 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http
protected Stack<KeyValuePair<Func<object, Task>, object>> _onStarting; protected Stack<KeyValuePair<Func<object, Task>, object>> _onStarting;
protected Stack<KeyValuePair<Func<object, Task>, object>> _onCompleted; protected Stack<KeyValuePair<Func<object, Task>, object>> _onCompleted;
private Task _requestProcessingTask;
protected volatile bool _requestProcessingStopping; // volatile, see: https://msdn.microsoft.com/en-us/library/x13ttww7.aspx protected volatile bool _requestProcessingStopping; // volatile, see: https://msdn.microsoft.com/en-us/library/x13ttww7.aspx
protected int _requestAborted; protected int _requestAborted;
private CancellationTokenSource _abortedCts; private CancellationTokenSource _abortedCts;
@ -104,7 +103,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http
public IPipeReader Input { get; set; } public IPipeReader Input { get; set; }
public ISocketOutput Output { get; set; } public ISocketOutput Output { get; set; }
public IEnumerable<IAdaptedConnection> AdaptedConnections { get; set; } public IAdaptedConnection[] AdaptedConnections { get; set; }
public ConnectionLifetimeControl LifetimeControl { get; set; } public ConnectionLifetimeControl LifetimeControl { get; set; }
public ITimeoutControl TimeoutControl { get; set; } public ITimeoutControl TimeoutControl { get; set; }
@ -294,26 +293,9 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http
public bool HasResponseStarted => _requestProcessingStatus == RequestProcessingStatus.ResponseStarted; public bool HasResponseStarted => _requestProcessingStatus == RequestProcessingStatus.ResponseStarted;
protected FrameRequestHeaders FrameRequestHeaders { get; private set; } protected FrameRequestHeaders FrameRequestHeaders { get; } = new FrameRequestHeaders();
protected FrameResponseHeaders FrameResponseHeaders { get; private set; } protected FrameResponseHeaders FrameResponseHeaders { get; } = new FrameResponseHeaders();
public void InitializeHeaders()
{
if (FrameRequestHeaders == null)
{
FrameRequestHeaders = new FrameRequestHeaders();
}
RequestHeaders = FrameRequestHeaders;
if (FrameResponseHeaders == null)
{
FrameResponseHeaders = new FrameResponseHeaders();
}
ResponseHeaders = FrameResponseHeaders;
}
public void InitializeStreams(MessageBody messageBody) public void InitializeStreams(MessageBody messageBody)
{ {
@ -333,9 +315,6 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http
public void Reset() public void Reset()
{ {
FrameRequestHeaders?.Reset();
FrameResponseHeaders?.Reset();
_onStarting = null; _onStarting = null;
_onCompleted = null; _onCompleted = null;
@ -366,6 +345,11 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http
LocalPort = LocalEndPoint?.Port ?? 0; LocalPort = LocalEndPoint?.Port ?? 0;
ConnectionIdFeature = ConnectionId; ConnectionIdFeature = ConnectionId;
FrameRequestHeaders.Reset();
FrameResponseHeaders.Reset();
RequestHeaders = FrameRequestHeaders;
ResponseHeaders = FrameResponseHeaders;
if (AdaptedConnections != null) if (AdaptedConnections != null)
{ {
try try
@ -393,27 +377,14 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http
} }
/// <summary> /// <summary>
/// Called once by Connection class to begin the RequestProcessingAsync loop. /// Stops the request processing loop between requests.
/// Called on all active connections when the server wants to initiate a shutdown
/// and after a keep-alive timeout.
/// </summary> /// </summary>
public void Start() public void Stop()
{
Reset();
_requestProcessingTask = RequestProcessingAsync();
}
/// <summary>
/// Should be called when the server wants to initiate a shutdown. The Task returned will
/// become complete when the RequestProcessingAsync function has exited. It is expected that
/// Stop will be called on all active connections, and Task.WaitAll() will be called on every
/// return value.
/// </summary>
public Task StopAsync()
{ {
_requestProcessingStopping = true; _requestProcessingStopping = true;
Input.CancelPendingRead(); Input.CancelPendingRead();
Debug.Assert(_requestProcessingTask != null);
return _requestProcessingTask ?? Task.CompletedTask;
} }
private void CancelRequestAbortedToken() private void CancelRequestAbortedToken()
@ -453,7 +424,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http
/// The resulting Task from this loop is preserved in a field which is used when the server needs /// The resulting Task from this loop is preserved in a field which is used when the server needs
/// to drain and close all currently active connections. /// to drain and close all currently active connections.
/// </summary> /// </summary>
public abstract Task RequestProcessingAsync(); public abstract Task ProcessRequestsAsync();
public void OnStarting(Func<object, Task> callback, object state) public void OnStarting(Func<object, Task> callback, object state)
{ {
@ -797,11 +768,6 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http
{ {
if (_requestRejectedException != null) if (_requestRejectedException != null)
{ {
if (FrameRequestHeaders == null || FrameResponseHeaders == null)
{
InitializeHeaders();
}
return ProduceEnd(); return ProduceEnd();
} }
@ -1121,11 +1087,6 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http
StatusCode = statusCode; StatusCode = statusCode;
ReasonPhrase = null; ReasonPhrase = null;
if (FrameResponseHeaders == null)
{
InitializeHeaders();
}
var responseHeaders = FrameResponseHeaders; var responseHeaders = FrameResponseHeaders;
responseHeaders.Reset(); responseHeaders.Reset();
var dateHeaderValues = DateHeaderValueManager.GetDateHeaderValues(); var dateHeaderValues = DateHeaderValueManager.GetDateHeaderValues();

View File

@ -28,7 +28,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http
/// The resulting Task from this loop is preserved in a field which is used when the server needs /// The resulting Task from this loop is preserved in a field which is used when the server needs
/// to drain and close all currently active connections. /// to drain and close all currently active connections.
/// </summary> /// </summary>
public override async Task RequestProcessingAsync() public override async Task ProcessRequestsAsync()
{ {
try try
{ {
@ -36,7 +36,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http
{ {
TimeoutControl.SetTimeout(_keepAliveTicks, TimeoutAction.CloseConnection); TimeoutControl.SetTimeout(_keepAliveTicks, TimeoutAction.CloseConnection);
InitializeHeaders(); Reset();
while (!_requestProcessingStopping) while (!_requestProcessingStopping)
{ {
@ -77,8 +77,8 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http
throw BadHttpRequestException.GetException( throw BadHttpRequestException.GetException(
RequestRejectionReason.InvalidRequestLine); RequestRejectionReason.InvalidRequestLine);
case RequestProcessingStatus.ParsingHeaders: case RequestProcessingStatus.ParsingHeaders:
throw BadHttpRequestException.GetException(RequestRejectionReason throw BadHttpRequestException.GetException(
.MalformedRequestInvalidHeaders); RequestRejectionReason.MalformedRequestInvalidHeaders);
} }
} }
} }
@ -196,15 +196,6 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http
// End the connection for non keep alive as data incoming may have been thrown off // End the connection for non keep alive as data incoming may have been thrown off
return; return;
} }
// Don't reset frame state if we're exiting the loop. This avoids losing request rejection
// information (for 4xx response), and prevents ObjectDisposedException on HTTPS (ODEs
// will be thrown if PrepareRequest is not null and references objects disposed on connection
// close - see https://github.com/aspnet/KestrelHttpServer/issues/1103#issuecomment-250237677).
if (!_requestProcessingStopping)
{
Reset();
}
} }
} }
catch (BadHttpRequestException ex) catch (BadHttpRequestException ex)

View File

@ -43,7 +43,13 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Https
public bool IsHttps => true; public bool IsHttps => true;
public async Task<IAdaptedConnection> OnConnectionAsync(ConnectionAdapterContext context) public Task<IAdaptedConnection> OnConnectionAsync(ConnectionAdapterContext context)
{
// Don't trust SslStream not to block.
return Task.Run(() => InnerOnConnectionAsync(context));
}
private async Task<IAdaptedConnection> InnerOnConnectionAsync(ConnectionAdapterContext context)
{ {
SslStream sslStream; SslStream sslStream;
bool certificateRequired; bool certificateRequired;
@ -127,6 +133,11 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Https
requestFeatures.Get<IHttpRequestFeature>().Scheme = "https"; requestFeatures.Get<IHttpRequestFeature>().Scheme = "https";
} }
public void Dispose()
{
_sslStream.Dispose();
}
} }
private class ClosedAdaptedConnection : IAdaptedConnection private class ClosedAdaptedConnection : IAdaptedConnection
@ -136,6 +147,10 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Https
public void PrepareRequest(IFeatureCollection requestFeatures) public void PrepareRequest(IFeatureCollection requestFeatures)
{ {
} }
public void Dispose()
{
}
} }
} }
} }

View File

@ -63,21 +63,27 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Transport.Libuv.Internal
Input = _connectionContext.Input; Input = _connectionContext.Input;
Output = new LibuvOutputConsumer(_connectionContext.Output, Thread, _socket, ConnectionId, Log); Output = new LibuvOutputConsumer(_connectionContext.Output, Thread, _socket, ConnectionId, Log);
// Start socket prior to applying the ConnectionAdapter
StartReading(); StartReading();
Exception error = null;
try try
{ {
// This *must* happen after socket.ReadStart // This *must* happen after socket.ReadStart
// The socket output consumer is the only thing that can close the connection. If the // The socket output consumer is the only thing that can close the connection. If the
// output pipe is already closed by the time we start then it's fine since, it'll close gracefully afterwards. // output pipe is already closed by the time we start then it's fine since, it'll close gracefully afterwards.
await Output.WriteOutputAsync(); await Output.WriteOutputAsync();
// Now, complete the input so that no more reads can happen
Input.Complete(new ConnectionAbortedException());
_connectionContext.Output.Complete();
_connectionContext.OnConnectionClosed(ex: null);
} }
catch (UvException ex) catch (UvException ex)
{ {
error = new IOException(ex.Message, ex); var ioEx = new IOException(ex.Message, ex);
Input.Complete(ioEx);
_connectionContext.Output.Complete(ioEx);
_connectionContext.OnConnectionClosed(ioEx);
} }
finally finally
{ {
@ -85,18 +91,11 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Transport.Libuv.Internal
// on the stream handle // on the stream handle
Input.CancelPendingFlush(); Input.CancelPendingFlush();
// Now, complete the input so that no more reads can happen
Input.Complete(new ConnectionAbortedException());
// Send a FIN // Send a FIN
Log.ConnectionWriteFin(ConnectionId); Log.ConnectionWriteFin(ConnectionId);
// We're done with the socket now // We're done with the socket now
_socket.Dispose(); _socket.Dispose();
// Tell the kestrel we're done with this connection
_connectionContext.OnConnectionClosed(error);
_connectionContext.Output.Complete(error);
} }
} }
catch (Exception e) catch (Exception e)

View File

@ -39,18 +39,14 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Tests
FrameConnectionManager frameConnectionManager, FrameConnectionManager frameConnectionManager,
Mock<IKestrelTrace> trace) Mock<IKestrelTrace> trace)
{ {
var serviceContext = new TestServiceContext
{
ConnectionManager = frameConnectionManager
};
// The FrameConnection constructor adds itself to the connection manager.
var frameConnection = new FrameConnection(new FrameConnectionContext var frameConnection = new FrameConnection(new FrameConnectionContext
{ {
ServiceContext = serviceContext, ServiceContext = new TestServiceContext(),
ConnectionId = connectionId ConnectionId = connectionId
}); });
frameConnectionManager.AddConnection(0, frameConnection);
var connectionCount = 0; var connectionCount = 0;
frameConnectionManager.Walk(_ => connectionCount++); frameConnectionManager.Walk(_ => connectionCount++);

View File

@ -6,8 +6,10 @@ using System.Collections.Generic;
using System.Globalization; using System.Globalization;
using Microsoft.AspNetCore.Http; using Microsoft.AspNetCore.Http;
using Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http; using Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http;
using Microsoft.AspNetCore.Server.Kestrel.Transport.Abstractions;
using Microsoft.AspNetCore.Testing; using Microsoft.AspNetCore.Testing;
using Microsoft.Extensions.Primitives; using Microsoft.Extensions.Primitives;
using Moq;
using Xunit; using Xunit;
namespace Microsoft.AspNetCore.Server.Kestrel.Core.Tests namespace Microsoft.AspNetCore.Server.Kestrel.Core.Tests
@ -19,12 +21,13 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Tests
{ {
var frameContext = new FrameContext var frameContext = new FrameContext
{ {
ServiceContext = new TestServiceContext() ServiceContext = new TestServiceContext(),
ConnectionInformation = Mock.Of<IConnectionInformation>()
}; };
var frame = new Frame<object>(application: null, frameContext: frameContext); var frame = new Frame<object>(application: null, frameContext: frameContext);
frame.InitializeHeaders(); frame.Reset();
IDictionary<string, StringValues> headers = frame.ResponseHeaders; IDictionary<string, StringValues> headers = frame.ResponseHeaders;

View File

@ -60,7 +60,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Tests
_frameContext = new FrameContext _frameContext = new FrameContext
{ {
ServiceContext = _serviceContext, ServiceContext = _serviceContext,
ConnectionInformation = new MockConnectionInformation() ConnectionInformation = Mock.Of<IConnectionInformation>()
}; };
_frame = new TestFrame<object>(application: null, context: _frameContext) _frame = new TestFrame<object>(application: null, context: _frameContext)
@ -71,7 +71,6 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Tests
}; };
_frame.Reset(); _frame.Reset();
_frame.InitializeHeaders();
} }
public void Dispose() public void Dispose()
@ -245,28 +244,28 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Tests
} }
[Fact] [Fact]
public void InitializeHeadersResetsRequestHeaders() public void ResetResetsRequestHeaders()
{ {
// Arrange // Arrange
var originalRequestHeaders = _frame.RequestHeaders; var originalRequestHeaders = _frame.RequestHeaders;
_frame.RequestHeaders = new FrameRequestHeaders(); _frame.RequestHeaders = new FrameRequestHeaders();
// Act // Act
_frame.InitializeHeaders(); _frame.Reset();
// Assert // Assert
Assert.Same(originalRequestHeaders, _frame.RequestHeaders); Assert.Same(originalRequestHeaders, _frame.RequestHeaders);
} }
[Fact] [Fact]
public void InitializeHeadersResetsResponseHeaders() public void ResetResetsResponseHeaders()
{ {
// Arrange // Arrange
var originalResponseHeaders = _frame.ResponseHeaders; var originalResponseHeaders = _frame.ResponseHeaders;
_frame.ResponseHeaders = new FrameResponseHeaders(); _frame.ResponseHeaders = new FrameResponseHeaders();
// Act // Act
_frame.InitializeHeaders(); _frame.Reset();
// Assert // Assert
Assert.Same(originalResponseHeaders, _frame.ResponseHeaders); Assert.Same(originalResponseHeaders, _frame.ResponseHeaders);
@ -463,17 +462,17 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Tests
} }
[Fact] [Fact]
public void RequestProcessingAsyncEnablesKeepAliveTimeout() public void ProcessRequestsAsyncEnablesKeepAliveTimeout()
{ {
var connectionControl = new Mock<ITimeoutControl>(); var connectionControl = new Mock<ITimeoutControl>();
_frame.TimeoutControl = connectionControl.Object; _frame.TimeoutControl = connectionControl.Object;
_frame.Start(); var requestProcessingTask = _frame.ProcessRequestsAsync();
var expectedKeepAliveTimeout = _serviceContext.ServerOptions.Limits.KeepAliveTimeout.Ticks; var expectedKeepAliveTimeout = _serviceContext.ServerOptions.Limits.KeepAliveTimeout.Ticks;
connectionControl.Verify(cc => cc.SetTimeout(expectedKeepAliveTimeout, TimeoutAction.CloseConnection)); connectionControl.Verify(cc => cc.SetTimeout(expectedKeepAliveTimeout, TimeoutAction.CloseConnection));
var requestProcessingTask = _frame.StopAsync(); _frame.Stop();
_input.Writer.Complete(); _input.Writer.Complete();
requestProcessingTask.Wait(); requestProcessingTask.Wait();
@ -553,12 +552,12 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Tests
[Fact] [Fact]
public async Task RequestProcessingTaskIsUnwrapped() public async Task RequestProcessingTaskIsUnwrapped()
{ {
_frame.Start(); var requestProcessingTask = _frame.ProcessRequestsAsync();
var data = Encoding.ASCII.GetBytes("GET / HTTP/1.1\r\nHost:\r\n\r\n"); var data = Encoding.ASCII.GetBytes("GET / HTTP/1.1\r\nHost:\r\n\r\n");
await _input.Writer.WriteAsync(data); await _input.Writer.WriteAsync(data);
var requestProcessingTask = _frame.StopAsync(); _frame.Stop();
Assert.IsNotType(typeof(Task<Task>), requestProcessingTask); Assert.IsNotType(typeof(Task<Task>), requestProcessingTask);
await requestProcessingTask.TimeoutAfter(TimeSpan.FromSeconds(10)); await requestProcessingTask.TimeoutAfter(TimeSpan.FromSeconds(10));
@ -677,7 +676,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Tests
var headers0 = MakeHeaders(header0Count); var headers0 = MakeHeaders(header0Count);
var headers1 = MakeHeaders(header1Count, header0Count); var headers1 = MakeHeaders(header1Count, header0Count);
var requestProcessingTask = _frame.RequestProcessingAsync(); var requestProcessingTask = _frame.ProcessRequestsAsync();
await _input.Writer.WriteAsync(Encoding.ASCII.GetBytes("GET / HTTP/1.0\r\n")); await _input.Writer.WriteAsync(Encoding.ASCII.GetBytes("GET / HTTP/1.0\r\n"));
await WaitForCondition(TimeSpan.FromSeconds(1), () => _frame.RequestHeaders != null); await WaitForCondition(TimeSpan.FromSeconds(1), () => _frame.RequestHeaders != null);
@ -711,7 +710,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Tests
var headers0 = MakeHeaders(header0Count); var headers0 = MakeHeaders(header0Count);
var headers1 = MakeHeaders(header1Count, header0Count); var headers1 = MakeHeaders(header1Count, header0Count);
var requestProcessingTask = _frame.RequestProcessingAsync(); var requestProcessingTask = _frame.ProcessRequestsAsync();
await _input.Writer.WriteAsync(Encoding.ASCII.GetBytes("GET / HTTP/1.0\r\n")); await _input.Writer.WriteAsync(Encoding.ASCII.GetBytes("GET / HTTP/1.0\r\n"));
await WaitForCondition(TimeSpan.FromSeconds(1), () => _frame.RequestHeaders != null); await WaitForCondition(TimeSpan.FromSeconds(1), () => _frame.RequestHeaders != null);
@ -830,16 +829,6 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Tests
} }
} }
private class MockConnectionInformation : IConnectionInformation
{
public IPEndPoint RemoteEndPoint { get; }
public IPEndPoint LocalEndPoint { get; }
public PipeFactory PipeFactory { get; }
public bool RequiresDispatch { get; }
public IScheduler InputWriterScheduler { get; }
public IScheduler OutputReaderScheduler { get; }
}
private class RequestHeadersWrapper : IHeaderDictionary private class RequestHeadersWrapper : IHeaderDictionary
{ {
IHeaderDictionary _innerHeaders; IHeaderDictionary _innerHeaders;

View File

@ -156,6 +156,10 @@ namespace Microsoft.AspNetCore.Server.Kestrel.FunctionalTests
public void PrepareRequest(IFeatureCollection requestFeatures) public void PrepareRequest(IFeatureCollection requestFeatures)
{ {
} }
public void Dispose()
{
}
} }
private class RewritingStream : Stream private class RewritingStream : Stream

View File

@ -97,7 +97,6 @@ namespace Microsoft.AspNetCore.Server.Kestrel.FunctionalTests
[Fact] [Fact]
public async Task DoesNotThrowObjectDisposedExceptionOnConnectionAbort() public async Task DoesNotThrowObjectDisposedExceptionOnConnectionAbort()
{ {
var x509Certificate2 = new X509Certificate2(TestResources.TestCertificatePath, "testPassword");
var loggerFactory = new HandshakeErrorLoggerFactory(); var loggerFactory = new HandshakeErrorLoggerFactory();
var hostBuilder = new WebHostBuilder() var hostBuilder = new WebHostBuilder()
.UseKestrel(options => .UseKestrel(options =>
@ -150,7 +149,6 @@ namespace Microsoft.AspNetCore.Server.Kestrel.FunctionalTests
public async Task DoesNotThrowObjectDisposedExceptionFromWriteAsyncAfterConnectionIsAborted() public async Task DoesNotThrowObjectDisposedExceptionFromWriteAsyncAfterConnectionIsAborted()
{ {
var tcs = new TaskCompletionSource<object>(); var tcs = new TaskCompletionSource<object>();
var x509Certificate2 = new X509Certificate2(TestResources.TestCertificatePath, "testPassword");
var loggerFactory = new HandshakeErrorLoggerFactory(); var loggerFactory = new HandshakeErrorLoggerFactory();
var hostBuilder = new WebHostBuilder() var hostBuilder = new WebHostBuilder()
.UseKestrel(options => .UseKestrel(options =>
@ -196,6 +194,39 @@ namespace Microsoft.AspNetCore.Server.Kestrel.FunctionalTests
await tcs.Task.TimeoutAfter(TimeSpan.FromSeconds(10)); await tcs.Task.TimeoutAfter(TimeSpan.FromSeconds(10));
} }
// Regression test for https://github.com/aspnet/KestrelHttpServer/issues/1693
[Fact]
public async Task DoesNotThrowObjectDisposedExceptionOnEmptyConnection()
{
var loggerFactory = new HandshakeErrorLoggerFactory();
var hostBuilder = new WebHostBuilder()
.UseKestrel(options =>
{
options.Listen(new IPEndPoint(IPAddress.Loopback, 0), listenOptions =>
{
listenOptions.UseHttps(TestResources.TestCertificatePath, "testPassword");
});
})
.UseLoggerFactory(loggerFactory)
.Configure(app => app.Run(httpContext => Task.CompletedTask));
using (var host = hostBuilder.Build())
{
host.Start();
using (var socket = await HttpClientSlim.GetSocket(new Uri($"https://127.0.0.1:{host.GetPort()}/")))
using (var stream = new NetworkStream(socket, ownsSocket: false))
using (var sslStream = new SslStream(stream, true, (sender, certificate, chain, errors) => true))
{
await sslStream.AuthenticateAsClientAsync("127.0.0.1", clientCertificates: null,
enabledSslProtocols: SslProtocols.Tls11 | SslProtocols.Tls12,
checkCertificateRevocation: false);
}
}
Assert.False(loggerFactory.ErrorLogger.ObjectDisposedExceptionLogged);
}
// Regression test for https://github.com/aspnet/KestrelHttpServer/pull/1197 // Regression test for https://github.com/aspnet/KestrelHttpServer/pull/1197
[Fact] [Fact]
public void ConnectionFilterDoesNotLeakBlock() public void ConnectionFilterDoesNotLeakBlock()

View File

@ -147,7 +147,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.FunctionalTests
context.Response.OnStarting(() => Task.Run(() => onStartingCalled = true)); context.Response.OnStarting(() => Task.Run(() => onStartingCalled = true));
context.Response.OnCompleted(() => Task.Run(() => onCompletedCalled = true)); context.Response.OnCompleted(() => Task.Run(() => onCompletedCalled = true));
// Prevent OnStarting call (see Frame<T>.RequestProcessingAsync()). // Prevent OnStarting call (see Frame<T>.ProcessRequestsAsync()).
throw new Exception(); throw new Exception();
}); });
}); });

View File

@ -70,8 +70,6 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Performance
ErrorUtilities.ThrowInvalidRequestLine(); ErrorUtilities.ThrowInvalidRequestLine();
} }
_frame.InitializeHeaders();
if (!_frame.TakeMessageHeaders(_buffer, out consumed, out examined)) if (!_frame.TakeMessageHeaders(_buffer, out consumed, out examined))
{ {
ErrorUtilities.ThrowInvalidRequestHeaders(); ErrorUtilities.ThrowInvalidRequestHeaders();
@ -91,7 +89,6 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Performance
private void ParseRequestHeaders() private void ParseRequestHeaders()
{ {
_frame.Reset(); _frame.Reset();
_frame.InitializeHeaders();
if (!_frame.TakeMessageHeaders(_buffer, out var consumed, out var examined)) if (!_frame.TakeMessageHeaders(_buffer, out var consumed, out var examined))
{ {

View File

@ -109,7 +109,6 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Performance
}; };
frame.Reset(); frame.Reset();
frame.InitializeHeaders();
return frame; return frame;
} }

View File

@ -148,8 +148,6 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Performance
readableBuffer = readableBuffer.Slice(consumed); readableBuffer = readableBuffer.Slice(consumed);
Frame.InitializeHeaders();
if (!Frame.TakeMessageHeaders(readableBuffer, out consumed, out examined)) if (!Frame.TakeMessageHeaders(readableBuffer, out consumed, out examined))
{ {
ErrorUtilities.ThrowInvalidRequestHeaders(); ErrorUtilities.ThrowInvalidRequestHeaders();
@ -187,8 +185,6 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Performance
result = Pipe.Reader.ReadAsync().GetAwaiter().GetResult(); result = Pipe.Reader.ReadAsync().GetAwaiter().GetResult();
readableBuffer = result.Buffer; readableBuffer = result.Buffer;
Frame.InitializeHeaders();
if (!Frame.TakeMessageHeaders(readableBuffer, out consumed, out examined)) if (!Frame.TakeMessageHeaders(readableBuffer, out consumed, out examined))
{ {
ErrorUtilities.ThrowInvalidRequestHeaders(); ErrorUtilities.ThrowInvalidRequestHeaders();

View File

@ -182,7 +182,6 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Performance
var frame = new Frame<object>(application: null, frameContext: frameContext); var frame = new Frame<object>(application: null, frameContext: frameContext);
frame.Reset(); frame.Reset();
frame.InitializeHeaders();
_responseHeadersDirect = (FrameResponseHeaders)frame.ResponseHeaders; _responseHeadersDirect = (FrameResponseHeaders)frame.ResponseHeaders;
var context = new DefaultHttpContext(frame); var context = new DefaultHttpContext(frame);
_response = new DefaultHttpResponse(context); _response = new DefaultHttpResponse(context);

View File

@ -138,7 +138,6 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Performance
}; };
frame.Reset(); frame.Reset();
frame.InitializeHeaders();
// Start writing // Start writing
var ignore = socketOutput.WriteOutputAsync(); var ignore = socketOutput.WriteOutputAsync();

View File

@ -4,7 +4,6 @@
using System.IO; using System.IO;
using System.Threading.Tasks; using System.Threading.Tasks;
using Microsoft.AspNetCore.Http.Features; using Microsoft.AspNetCore.Http.Features;
using Microsoft.AspNetCore.Server.Kestrel.Core.Adapter;
using Microsoft.AspNetCore.Server.Kestrel.Core.Adapter.Internal; using Microsoft.AspNetCore.Server.Kestrel.Core.Adapter.Internal;
namespace Microsoft.AspNetCore.Testing namespace Microsoft.AspNetCore.Testing
@ -31,6 +30,10 @@ namespace Microsoft.AspNetCore.Testing
public void PrepareRequest(IFeatureCollection requestFeatures) public void PrepareRequest(IFeatureCollection requestFeatures)
{ {
} }
public void Dispose()
{
}
} }
} }
} }