More FrameConnection refactoring (#1820)

* More FrameConnection refactoring
- This change reverts the change to complete the writer with an
exception on abort because of the number of first chance exceptions
that get thrown.
- This change also moves connection logging into FrameConnection instead
of being split between the ConnectionHandler and FrameConnection.
- Fixed issues with LibuvOutputConsumerTests that leak WriteReq since
cancelled writes no longer end the connection.
This commit is contained in:
David Fowler 2017-05-09 17:40:25 -07:00 committed by GitHub
parent c48113ad80
commit c8b6a2be56
12 changed files with 100 additions and 82 deletions

View File

@ -7,7 +7,6 @@ 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.Core.Internal.Infrastructure;
using Microsoft.AspNetCore.Server.Kestrel.Internal.System.IO.Pipelines; using Microsoft.AspNetCore.Server.Kestrel.Internal.System.IO.Pipelines;
using Microsoft.AspNetCore.Server.Kestrel.Transport.Abstractions;
namespace Microsoft.AspNetCore.Server.Kestrel.Core.Adapter.Internal namespace Microsoft.AspNetCore.Server.Kestrel.Core.Adapter.Internal
{ {
@ -16,17 +15,17 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Adapter.Internal
private const int MinAllocBufferSize = 2048; private const int MinAllocBufferSize = 2048;
private readonly IKestrelTrace _trace; private readonly IKestrelTrace _trace;
private readonly IPipeWriter _transportOutputPipeWriter; private readonly IPipe _transportOutputPipe;
private readonly IPipeReader _transportInputPipeReader; private readonly IPipeReader _transportInputPipeReader;
public AdaptedPipeline(IPipeReader transportInputPipeReader, public AdaptedPipeline(IPipeReader transportInputPipeReader,
IPipeWriter transportOutputPipeWriter, IPipe transportOutputPipe,
IPipe inputPipe, IPipe inputPipe,
IPipe outputPipe, IPipe outputPipe,
IKestrelTrace trace) IKestrelTrace trace)
{ {
_transportInputPipeReader = transportInputPipeReader; _transportInputPipeReader = transportInputPipeReader;
_transportOutputPipeWriter = transportOutputPipeWriter; _transportOutputPipe = transportOutputPipe;
Input = inputPipe; Input = inputPipe;
Output = outputPipe; Output = outputPipe;
_trace = trace; _trace = trace;
@ -58,18 +57,24 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Adapter.Internal
while (true) while (true)
{ {
var readResult = await Output.Reader.ReadAsync(); var result = await Output.Reader.ReadAsync();
var buffer = readResult.Buffer; var buffer = result.Buffer;
try try
{ {
if (buffer.IsEmpty && readResult.IsCompleted) if (result.IsCancelled)
{ {
// Forward the cancellation to the transport pipe
_transportOutputPipe.Reader.CancelPendingRead();
break; break;
} }
if (buffer.IsEmpty) if (buffer.IsEmpty)
{ {
if (result.IsCompleted)
{
break;
}
await stream.FlushAsync(); await stream.FlushAsync();
} }
else if (buffer.IsSingleSpan) else if (buffer.IsSingleSpan)
@ -99,7 +104,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Adapter.Internal
finally finally
{ {
Output.Reader.Complete(); Output.Reader.Complete();
_transportOutputPipeWriter.Complete(error); _transportOutputPipe.Writer.Complete(error);
} }
} }
@ -111,8 +116,8 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Adapter.Internal
{ {
if (stream == null) if (stream == null)
{ {
// If the stream is null then we're going to abort the connection // REVIEW: Do we need an exception here?
throw new ConnectionAbortedException(); return;
} }
while (true) while (true)

View File

@ -47,16 +47,13 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal
ConnectionId = connectionId, ConnectionId = connectionId,
FrameConnectionId = frameConnectionId, FrameConnectionId = frameConnectionId,
ServiceContext = _serviceContext, ServiceContext = _serviceContext,
PipeFactory = connectionInfo.PipeFactory, ConnectionInformation = connectionInfo,
ConnectionAdapters = _listenOptions.ConnectionAdapters, ConnectionAdapters = _listenOptions.ConnectionAdapters,
Frame = frame, Frame = frame,
Input = inputPipe, Input = inputPipe,
Output = outputPipe, Output = outputPipe,
}); });
_serviceContext.Log.ConnectionStart(connectionId);
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.ProcessRequestsAsync is guaranteed to unblock the transport thread before calling // Frame.ProcessRequestsAsync is guaranteed to unblock the transport thread before calling
// application code. // application code.

View File

@ -40,7 +40,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal
public IPipeWriter Input => _context.Input.Writer; public IPipeWriter Input => _context.Input.Writer;
public IPipeReader Output => _context.Output.Reader; public IPipeReader Output => _context.Output.Reader;
private PipeFactory PipeFactory => _context.PipeFactory; private PipeFactory PipeFactory => _context.ConnectionInformation.PipeFactory;
// Internal for testing // Internal for testing
internal PipeOptions AdaptedInputPipeOptions => new PipeOptions internal PipeOptions AdaptedInputPipeOptions => new PipeOptions
@ -70,21 +70,24 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal
{ {
try try
{ {
Log.ConnectionStart(ConnectionId);
KestrelEventSource.Log.ConnectionStart(this, _context.ConnectionInformation);
AdaptedPipeline adaptedPipeline = null; AdaptedPipeline adaptedPipeline = null;
var adaptedPipelineTask = Task.CompletedTask; var adaptedPipelineTask = Task.CompletedTask;
var input = _context.Input.Reader; var input = _context.Input.Reader;
var output = _context.Output.Writer; var output = _context.Output;
if (_connectionAdapters.Count > 0) if (_connectionAdapters.Count > 0)
{ {
adaptedPipeline = new AdaptedPipeline(_context.Input.Reader, adaptedPipeline = new AdaptedPipeline(input,
_context.Output.Writer, output,
PipeFactory.Create(AdaptedInputPipeOptions), PipeFactory.Create(AdaptedInputPipeOptions),
PipeFactory.Create(AdaptedOutputPipeOptions), PipeFactory.Create(AdaptedOutputPipeOptions),
Log); Log);
input = adaptedPipeline.Input.Reader; input = adaptedPipeline.Input.Reader;
output = adaptedPipeline.Output.Writer; output = adaptedPipeline.Output;
} }
// Set these before the first await, this is to make sure that we don't yield control // Set these before the first await, this is to make sure that we don't yield control
@ -114,6 +117,9 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal
{ {
_context.ServiceContext.ConnectionManager.RemoveConnection(_context.FrameConnectionId); _context.ServiceContext.ConnectionManager.RemoveConnection(_context.FrameConnectionId);
DisposeAdaptedConnections(); DisposeAdaptedConnections();
Log.ConnectionStop(ConnectionId);
KestrelEventSource.Log.ConnectionStop(this);
} }
} }
@ -122,8 +128,6 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal
// Abort the connection (if not already aborted) // Abort the connection (if not already aborted)
_frame.Abort(ex); _frame.Abort(ex);
Log.ConnectionStop(ConnectionId);
KestrelEventSource.Log.ConnectionStop(this);
_socketClosedTcs.TrySetResult(null); _socketClosedTcs.TrySetResult(null);
} }

View File

@ -5,6 +5,7 @@ using System.Collections.Generic;
using Microsoft.AspNetCore.Server.Kestrel.Core.Adapter.Internal; using Microsoft.AspNetCore.Server.Kestrel.Core.Adapter.Internal;
using Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http; using Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http;
using Microsoft.AspNetCore.Server.Kestrel.Internal.System.IO.Pipelines; using Microsoft.AspNetCore.Server.Kestrel.Internal.System.IO.Pipelines;
using Microsoft.AspNetCore.Server.Kestrel.Transport.Abstractions;
namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal
{ {
@ -13,8 +14,8 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal
public string ConnectionId { get; set; } public string ConnectionId { get; set; }
public long FrameConnectionId { get; set; } public long FrameConnectionId { get; set; }
public ServiceContext ServiceContext { get; set; } public ServiceContext ServiceContext { get; set; }
public PipeFactory PipeFactory { get; set; }
public List<IConnectionAdapter> ConnectionAdapters { get; set; } public List<IConnectionAdapter> ConnectionAdapters { get; set; }
public IConnectionInformation ConnectionInformation { get; set; }
public Frame Frame { get; set; } public Frame Frame { get; set; }
public IPipe Input { get; set; } public IPipe Input { get; set; }

View File

@ -6,7 +6,6 @@ using System.Threading;
using System.Threading.Tasks; using System.Threading.Tasks;
using Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Infrastructure; 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.AspNetCore.Server.Kestrel.Transport.Abstractions;
using Microsoft.Extensions.Internal; using Microsoft.Extensions.Internal;
namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http
@ -21,10 +20,9 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http
// This locks access to to all of the below fields // This locks access to to all of the below fields
private readonly object _contextLock = new object(); private readonly object _contextLock = new object();
private bool _cancelled = false;
private bool _completed = false; private bool _completed = false;
private readonly IPipeWriter _pipe; private readonly IPipe _pipe;
// https://github.com/dotnet/corefxlab/issues/1334 // https://github.com/dotnet/corefxlab/issues/1334
// Pipelines don't support multiple awaiters on flush // Pipelines don't support multiple awaiters on flush
@ -33,7 +31,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http
private readonly object _flushLock = new object(); private readonly object _flushLock = new object();
private Action _flushCompleted; private Action _flushCompleted;
public OutputProducer(IPipeWriter pipe, string connectionId, IKestrelTrace log) public OutputProducer(IPipe pipe, string connectionId, IKestrelTrace log)
{ {
_pipe = pipe; _pipe = pipe;
_connectionId = connectionId; _connectionId = connectionId;
@ -50,13 +48,8 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http
{ {
if (cancellationToken.IsCancellationRequested) if (cancellationToken.IsCancellationRequested)
{ {
_cancelled = true;
return Task.FromCanceled(cancellationToken); return Task.FromCanceled(cancellationToken);
} }
else if (_cancelled)
{
return TaskCache.CompletedTask;
}
return WriteAsync(buffer, cancellationToken, chunk); return WriteAsync(buffer, cancellationToken, chunk);
} }
@ -80,7 +73,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http
return; return;
} }
var buffer = _pipe.Alloc(1); var buffer = _pipe.Writer.Alloc(1);
callback(buffer, state); callback(buffer, state);
buffer.Commit(); buffer.Commit();
} }
@ -97,7 +90,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http
_log.ConnectionDisconnect(_connectionId); _log.ConnectionDisconnect(_connectionId);
_completed = true; _completed = true;
_pipe.Complete(); _pipe.Writer.Complete();
} }
} }
@ -112,7 +105,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http
_log.ConnectionDisconnect(_connectionId); _log.ConnectionDisconnect(_connectionId);
_completed = true; _completed = true;
_pipe.Complete(new ConnectionAbortedException()); _pipe.Reader.CancelPendingRead();
} }
} }
@ -130,7 +123,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http
return TaskCache.CompletedTask; return TaskCache.CompletedTask;
} }
writableBuffer = _pipe.Alloc(1); writableBuffer = _pipe.Writer.Alloc(1);
var writer = new WritableBufferWriter(writableBuffer); var writer = new WritableBufferWriter(writableBuffer);
if (buffer.Count > 0) if (buffer.Count > 0)
{ {

View File

@ -37,50 +37,47 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Transport.Libuv.Internal
while (true) while (true)
{ {
var result = await _pipe.ReadAsync();
var buffer = result.Buffer;
var consumed = buffer.End;
try try
{ {
var result = await _pipe.ReadAsync(); if (result.IsCancelled)
var buffer = result.Buffer;
var consumed = buffer.End;
try
{ {
if (!buffer.IsEmpty) break;
}
if (!buffer.IsEmpty)
{
var writeReq = pool.Allocate();
try
{ {
var writeReq = pool.Allocate(); var writeResult = await writeReq.WriteAsync(_socket, buffer);
try LogWriteInfo(writeResult.Status, writeResult.Error);
if (writeResult.Error != null)
{ {
var writeResult = await writeReq.WriteAsync(_socket, buffer); consumed = buffer.Start;
throw writeResult.Error;
LogWriteInfo(writeResult.Status, writeResult.Error);
if (writeResult.Error != null)
{
consumed = buffer.Start;
throw writeResult.Error;
}
}
finally
{
// Make sure we return the writeReq to the pool
pool.Return(writeReq);
} }
} }
finally
if (buffer.IsEmpty && result.IsCompleted)
{ {
break; // Make sure we return the writeReq to the pool
pool.Return(writeReq);
} }
} }
finally else if (result.IsCompleted)
{ {
_pipe.Advance(consumed); break;
} }
} }
catch (ConnectionAbortedException) finally
{ {
break; _pipe.Advance(consumed);
} }
} }
} }

View File

@ -167,6 +167,11 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Transport.Sockets
var result = await _output.ReadAsync(); var result = await _output.ReadAsync();
var buffer = result.Buffer; var buffer = result.Buffer;
if (result.IsCancelled)
{
break;
}
try try
{ {
if (!buffer.IsEmpty) if (!buffer.IsEmpty)
@ -189,15 +194,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Transport.Sockets
} }
} }
} }
else if (result.IsCompleted)
if (result.IsCancelled)
{
// Send a FIN
_socket.Shutdown(SocketShutdown.Send);
break;
}
if (buffer.IsEmpty && result.IsCompleted)
{ {
break; break;
} }
@ -207,6 +204,8 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Transport.Sockets
_output.Advance(buffer.End); _output.Advance(buffer.End);
} }
} }
_socket.Shutdown(SocketShutdown.Send);
} }
catch (SocketException ex) when (ex.SocketErrorCode == SocketError.OperationAborted) catch (SocketException ex) when (ex.SocketErrorCode == SocketError.OperationAborted)
{ {
@ -216,10 +215,6 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Transport.Sockets
{ {
error = null; error = null;
} }
catch (ConnectionAbortedException)
{
error = null;
}
catch (IOException ex) catch (IOException ex)
{ {
error = ex; error = ex;

View File

@ -70,7 +70,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Tests
TimeoutControl = Mock.Of<ITimeoutControl>() TimeoutControl = Mock.Of<ITimeoutControl>()
}; };
_frame.Output = new OutputProducer(output.Writer, "", Mock.Of<IKestrelTrace>()); _frame.Output = new OutputProducer(output, "", Mock.Of<IKestrelTrace>());
_frame.Reset(); _frame.Reset();
} }

View File

@ -53,7 +53,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Tests
var pipe = _pipeFactory.Create(pipeOptions); var pipe = _pipeFactory.Create(pipeOptions);
var serviceContext = new TestServiceContext(); var serviceContext = new TestServiceContext();
var frame = new Frame<object>(null, new FrameContext { ServiceContext = serviceContext }); var frame = new Frame<object>(null, new FrameContext { ServiceContext = serviceContext });
var socketOutput = new OutputProducer(pipe.Writer, "0", serviceContext.Log); var socketOutput = new OutputProducer(pipe, "0", serviceContext.Log);
return socketOutput; return socketOutput;
} }

View File

@ -109,7 +109,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Performance
Input = input.Reader, Input = input.Reader,
}; };
frame.Output = new OutputProducer(output.Writer, "", null); frame.Output = new OutputProducer(output, "", null);
frame.Reset(); frame.Reset();

View File

@ -131,7 +131,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Performance
{ {
Input = input.Reader, Input = input.Reader,
}; };
frame.Output = new OutputProducer(output.Writer, "", null); frame.Output = new OutputProducer(output, "", null);
frame.Reset(); frame.Reset();

View File

@ -392,6 +392,14 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Transport.Libuv.Tests
Assert.True(task3Canceled.IsCanceled); Assert.True(task3Canceled.IsCanceled);
Assert.True(abortedSource.IsCancellationRequested); Assert.True(abortedSource.IsCancellationRequested);
await _mockLibuv.OnPostTask;
// Complete the 4th write
while (completeQueue.TryDequeue(out var triggerNextCompleted))
{
await _libuvThread.PostAsync(cb => cb(0), triggerNextCompleted);
}
} }
}); });
} }
@ -467,6 +475,14 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Transport.Libuv.Tests
Assert.True(task3Canceled.IsCanceled); Assert.True(task3Canceled.IsCanceled);
Assert.True(abortedSource.IsCancellationRequested); Assert.True(abortedSource.IsCancellationRequested);
await _mockLibuv.OnPostTask;
// Complete the 4th write
while (completeQueue.TryDequeue(out var triggerNextCompleted))
{
await _libuvThread.PostAsync(cb => cb(0), triggerNextCompleted);
}
} }
}); });
} }
@ -544,6 +560,14 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Transport.Libuv.Tests
// Third task is now canceled // Third task is now canceled
await Assert.ThrowsAsync<OperationCanceledException>(() => task3Canceled); await Assert.ThrowsAsync<OperationCanceledException>(() => task3Canceled);
Assert.True(task3Canceled.IsCanceled); Assert.True(task3Canceled.IsCanceled);
await _mockLibuv.OnPostTask;
// Complete the 4th write
while (completeQueue.TryDequeue(out var triggerNextCompleted))
{
await _libuvThread.PostAsync(cb => cb(0), triggerNextCompleted);
}
} }
}); });
} }
@ -586,6 +610,8 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Transport.Libuv.Tests
var writeTask2 = socketOutput.WriteAsync(buffer); var writeTask2 = socketOutput.WriteAsync(buffer);
var writeTask3 = socketOutput.WriteAsync(buffer); var writeTask3 = socketOutput.WriteAsync(buffer);
await _mockLibuv.OnPostTask;
// Drain the write queue // Drain the write queue
while (completeQueue.TryDequeue(out var triggerNextCompleted)) while (completeQueue.TryDequeue(out var triggerNextCompleted))
{ {
@ -670,7 +696,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Transport.Libuv.Tests
var frame = new Frame<object>(null, new FrameContext { ServiceContext = serviceContext }); var frame = new Frame<object>(null, new FrameContext { ServiceContext = serviceContext });
var socket = new MockSocket(_mockLibuv, _libuvThread.Loop.ThreadId, transportContext.Log); var socket = new MockSocket(_mockLibuv, _libuvThread.Loop.ThreadId, transportContext.Log);
var outputProducer = new OutputProducer(pipe.Writer, "0", serviceContext.Log); var outputProducer = new OutputProducer(pipe, "0", serviceContext.Log);
var consumer = new LibuvOutputConsumer(pipe.Reader, _libuvThread, socket, "0", transportContext.Log); var consumer = new LibuvOutputConsumer(pipe.Reader, _libuvThread, socket, "0", transportContext.Log);
frame.Output = outputProducer; frame.Output = outputProducer;