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

View File

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

View File

@ -40,7 +40,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal
public IPipeWriter Input => _context.Input.Writer;
public IPipeReader Output => _context.Output.Reader;
private PipeFactory PipeFactory => _context.PipeFactory;
private PipeFactory PipeFactory => _context.ConnectionInformation.PipeFactory;
// Internal for testing
internal PipeOptions AdaptedInputPipeOptions => new PipeOptions
@ -70,21 +70,24 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal
{
try
{
Log.ConnectionStart(ConnectionId);
KestrelEventSource.Log.ConnectionStart(this, _context.ConnectionInformation);
AdaptedPipeline adaptedPipeline = null;
var adaptedPipelineTask = Task.CompletedTask;
var input = _context.Input.Reader;
var output = _context.Output.Writer;
var output = _context.Output;
if (_connectionAdapters.Count > 0)
{
adaptedPipeline = new AdaptedPipeline(_context.Input.Reader,
_context.Output.Writer,
adaptedPipeline = new AdaptedPipeline(input,
output,
PipeFactory.Create(AdaptedInputPipeOptions),
PipeFactory.Create(AdaptedOutputPipeOptions),
Log);
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
@ -114,6 +117,9 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal
{
_context.ServiceContext.ConnectionManager.RemoveConnection(_context.FrameConnectionId);
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)
_frame.Abort(ex);
Log.ConnectionStop(ConnectionId);
KestrelEventSource.Log.ConnectionStop(this);
_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.Internal.Http;
using Microsoft.AspNetCore.Server.Kestrel.Internal.System.IO.Pipelines;
using Microsoft.AspNetCore.Server.Kestrel.Transport.Abstractions;
namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal
{
@ -13,8 +14,8 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal
public string ConnectionId { get; set; }
public long FrameConnectionId { get; set; }
public ServiceContext ServiceContext { get; set; }
public PipeFactory PipeFactory { get; set; }
public List<IConnectionAdapter> ConnectionAdapters { get; set; }
public IConnectionInformation ConnectionInformation { get; set; }
public Frame Frame { get; set; }
public IPipe Input { get; set; }

View File

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

View File

@ -37,50 +37,47 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Transport.Libuv.Internal
while (true)
{
var result = await _pipe.ReadAsync();
var buffer = result.Buffer;
var consumed = buffer.End;
try
{
var result = await _pipe.ReadAsync();
var buffer = result.Buffer;
var consumed = buffer.End;
try
if (result.IsCancelled)
{
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);
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);
consumed = buffer.Start;
throw writeResult.Error;
}
}
if (buffer.IsEmpty && result.IsCompleted)
finally
{
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 buffer = result.Buffer;
if (result.IsCancelled)
{
break;
}
try
{
if (!buffer.IsEmpty)
@ -189,15 +194,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Transport.Sockets
}
}
}
if (result.IsCancelled)
{
// Send a FIN
_socket.Shutdown(SocketShutdown.Send);
break;
}
if (buffer.IsEmpty && result.IsCompleted)
else if (result.IsCompleted)
{
break;
}
@ -207,6 +204,8 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Transport.Sockets
_output.Advance(buffer.End);
}
}
_socket.Shutdown(SocketShutdown.Send);
}
catch (SocketException ex) when (ex.SocketErrorCode == SocketError.OperationAborted)
{
@ -216,10 +215,6 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Transport.Sockets
{
error = null;
}
catch (ConnectionAbortedException)
{
error = null;
}
catch (IOException ex)
{
error = ex;

View File

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

View File

@ -53,7 +53,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Tests
var pipe = _pipeFactory.Create(pipeOptions);
var serviceContext = new TestServiceContext();
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;
}

View File

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

View File

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

View File

@ -392,6 +392,14 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Transport.Libuv.Tests
Assert.True(task3Canceled.IsCanceled);
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(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
await Assert.ThrowsAsync<OperationCanceledException>(() => task3Canceled);
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 writeTask3 = socketOutput.WriteAsync(buffer);
await _mockLibuv.OnPostTask;
// Drain the write queue
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 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);
frame.Output = outputProducer;