Refactoring and of FrameConnection and Frame (#1816)

* Refactoring and of FrameConnection and Frame
- Building on top of the last refactoring of FrameConnection, this change aims to clean up
the communication between the Frame and FrameConnection by removing some concepts and
being consistent about the communication between Frame and FrameConnection with or without
connection adapters. Changes include:
- Removing ConnectionLifetimeControl, ISocketOutput, StreamSocketOutput
- Moving more initialization of the frame to FrameConnection after the pipes
are setup
- OutputProducer communicates cancellation via the IPipeWriter instead of the output's IPipeReader.
- Frame always communicates via the pipes and that communications flows through the layers to the transport.
This means that each 1/2 of the adapted pipeline handles closing the right side of the transport at the
right time, propagating exceptions as necessary.
- This is how the flow looks now:
            ->                        ->
[transport]     [connection adapters]     [frame]
            <-                        <-
- Transports need to handle a ConnectionAbortedException on the output as a signal to stop
writing and end the connection. This will no longer try to drain the output but will just stop
writing and end the response immediately.
- Remove frame.Abort when cancellation on Write fails.
- Unify the connection shutdown logic
- Dispose 1/2 initialized connection adapters

#1815
This commit is contained in:
David Fowler 2017-05-08 20:44:13 -07:00 committed by GitHub
parent 6e2fdda162
commit c48113ad80
25 changed files with 419 additions and 638 deletions

View File

@ -1,6 +1,6 @@
Microsoft Visual Studio Solution File, Format Version 12.00 Microsoft Visual Studio Solution File, Format Version 12.00
# Visual Studio 15 # Visual Studio 15
VisualStudioVersion = 15.0.26405.0 VisualStudioVersion = 15.0.26403.7
MinimumVisualStudioVersion = 10.0.40219.1 MinimumVisualStudioVersion = 10.0.40219.1
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Solution Items", "Solution Items", "{7972A5D6-3385-4127-9277-428506DD44FF}" Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Solution Items", "Solution Items", "{7972A5D6-3385-4127-9277-428506DD44FF}"
ProjectSection(SolutionItems) = preProject ProjectSection(SolutionItems) = preProject
@ -26,7 +26,6 @@ Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "shared", "shared", "{0EF2AC
test\shared\LifetimeNotImplemented.cs = test\shared\LifetimeNotImplemented.cs test\shared\LifetimeNotImplemented.cs = test\shared\LifetimeNotImplemented.cs
test\shared\MockFrameControl.cs = test\shared\MockFrameControl.cs test\shared\MockFrameControl.cs = test\shared\MockFrameControl.cs
test\shared\MockLogger.cs = test\shared\MockLogger.cs test\shared\MockLogger.cs = test\shared\MockLogger.cs
test\shared\MockSocketOutput.cs = test\shared\MockSocketOutput.cs
test\shared\MockSystemClock.cs = test\shared\MockSystemClock.cs test\shared\MockSystemClock.cs = test\shared\MockSystemClock.cs
test\shared\StringExtensions.cs = test\shared\StringExtensions.cs test\shared\StringExtensions.cs = test\shared\StringExtensions.cs
test\shared\TestApp.cs = test\shared\TestApp.cs test\shared\TestApp.cs = test\shared\TestApp.cs

View File

@ -7,7 +7,7 @@ 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.Extensions.Logging; using Microsoft.AspNetCore.Server.Kestrel.Transport.Abstractions;
namespace Microsoft.AspNetCore.Server.Kestrel.Core.Adapter.Internal namespace Microsoft.AspNetCore.Server.Kestrel.Core.Adapter.Internal
{ {
@ -15,77 +15,149 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Adapter.Internal
{ {
private const int MinAllocBufferSize = 2048; private const int MinAllocBufferSize = 2048;
private readonly Stream _filteredStream;
private readonly StreamSocketOutput _output;
private readonly IKestrelTrace _trace; private readonly IKestrelTrace _trace;
private readonly IPipeWriter _transportOutputPipeWriter;
private readonly IPipeReader _transportInputPipeReader;
public AdaptedPipeline( public AdaptedPipeline(IPipeReader transportInputPipeReader,
Stream filteredStream, IPipeWriter transportOutputPipeWriter,
IPipe inputPipe, IPipe inputPipe,
IPipe outputPipe, IPipe outputPipe,
IKestrelTrace trace) IKestrelTrace trace)
{ {
_transportInputPipeReader = transportInputPipeReader;
_transportOutputPipeWriter = transportOutputPipeWriter;
Input = inputPipe; Input = inputPipe;
_output = new StreamSocketOutput(filteredStream, outputPipe); Output = outputPipe;
_filteredStream = filteredStream;
_trace = trace; _trace = trace;
} }
public IPipe Input { get; } public IPipe Input { get; }
public ISocketOutput Output => _output; public IPipe Output { get; }
public async Task RunAsync() public async Task RunAsync(Stream stream)
{ {
try var inputTask = ReadInputAsync(stream);
{ var outputTask = WriteOutputAsync(stream);
var inputTask = ReadInputAsync();
var outputTask = _output.WriteOutputAsync();
await inputTask; await inputTask;
await outputTask;
_output.Dispose();
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 WriteOutputAsync(Stream stream)
{ {
int bytesRead; Exception error = null;
do try
{ {
var block = Input.Writer.Alloc(MinAllocBufferSize); if (stream == null)
try
{ {
var array = block.Buffer.GetArray(); return;
}
while (true)
{
var readResult = await Output.Reader.ReadAsync();
var buffer = readResult.Buffer;
try try
{ {
bytesRead = await _filteredStream.ReadAsync(array.Array, array.Offset, array.Count); if (buffer.IsEmpty && readResult.IsCompleted)
block.Advance(bytesRead); {
break;
}
if (buffer.IsEmpty)
{
await stream.FlushAsync();
}
else if (buffer.IsSingleSpan)
{
var array = buffer.First.GetArray();
await stream.WriteAsync(array.Array, array.Offset, array.Count);
}
else
{
foreach (var memory in buffer)
{
var array = memory.GetArray();
await stream.WriteAsync(array.Array, array.Offset, array.Count);
}
}
} }
finally finally
{ {
await block.FlushAsync(); Output.Reader.Advance(buffer.End);
} }
} }
catch (Exception ex) }
catch (Exception ex)
{
error = ex;
}
finally
{
Output.Reader.Complete();
_transportOutputPipeWriter.Complete(error);
}
}
private async Task ReadInputAsync(Stream stream)
{
Exception error = null;
try
{
if (stream == null)
{ {
Input.Writer.Complete(ex); // If the stream is null then we're going to abort the connection
throw new ConnectionAbortedException();
// Don't rethrow the exception. It should be handled by the Pipeline consumer.
return;
} }
} while (bytesRead != 0);
Input.Writer.Complete(); while (true)
{
var outputBuffer = Input.Writer.Alloc(MinAllocBufferSize);
var array = outputBuffer.Buffer.GetArray();
try
{
var bytesRead = await stream.ReadAsync(array.Array, array.Offset, array.Count);
outputBuffer.Advance(bytesRead);
if (bytesRead == 0)
{
// FIN
break;
}
}
finally
{
outputBuffer.Commit();
}
var result = await outputBuffer.FlushAsync();
if (result.IsCompleted)
{
break;
}
}
}
catch (Exception ex)
{
// Don't rethrow the exception. It should be handled by the Pipeline consumer.
error = ex;
}
finally
{
Input.Writer.Complete(error);
// The application could have ended the input pipe so complete
// the transport pipe as well
_transportInputPipeReader.Complete();
}
} }
} }
} }

View File

@ -5,7 +5,6 @@ using System;
using System.IO; using System.IO;
using System.Threading; using System.Threading;
using System.Threading.Tasks; using System.Threading.Tasks;
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;
namespace Microsoft.AspNetCore.Server.Kestrel.Core.Adapter.Internal namespace Microsoft.AspNetCore.Server.Kestrel.Core.Adapter.Internal
@ -13,9 +12,9 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Adapter.Internal
public class RawStream : Stream public class RawStream : Stream
{ {
private readonly IPipeReader _input; private readonly IPipeReader _input;
private readonly ISocketOutput _output; private readonly IPipeWriter _output;
public RawStream(IPipeReader input, ISocketOutput output) public RawStream(IPipeReader input, IPipeWriter output)
{ {
_input = input; _input = input;
_output = output; _output = output;
@ -71,19 +70,10 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Adapter.Internal
public override void Write(byte[] buffer, int offset, int count) public override void Write(byte[] buffer, int offset, int count)
{ {
ArraySegment<byte> segment; WriteAsync(buffer, offset, count).GetAwaiter().GetResult();
if (buffer != null)
{
segment = new ArraySegment<byte>(buffer, offset, count);
}
else
{
segment = default(ArraySegment<byte>);
}
_output.Write(segment);
} }
public override Task WriteAsync(byte[] buffer, int offset, int count, CancellationToken token) public override async Task WriteAsync(byte[] buffer, int offset, int count, CancellationToken token)
{ {
ArraySegment<byte> segment; ArraySegment<byte> segment;
if (buffer != null) if (buffer != null)
@ -94,17 +84,19 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Adapter.Internal
{ {
segment = default(ArraySegment<byte>); segment = default(ArraySegment<byte>);
} }
return _output.WriteAsync(segment, cancellationToken: token); var output = _output.Alloc();
output.Write(segment);
await output.FlushAsync(token);
} }
public override void Flush() public override void Flush()
{ {
_output.Flush(); FlushAsync(CancellationToken.None).GetAwaiter().GetResult();
} }
public override Task FlushAsync(CancellationToken cancellationToken) public override Task FlushAsync(CancellationToken cancellationToken)
{ {
return _output.FlushAsync(cancellationToken); return WriteAsync(null, 0, 0, cancellationToken);
} }
private async Task<int> ReadAsync(ArraySegment<byte> buffer) private async Task<int> ReadAsync(ArraySegment<byte> buffer)
@ -209,11 +201,5 @@ 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

@ -1,145 +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;
using System.IO;
using System.Threading;
using System.Threading.Tasks;
using Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http;
using Microsoft.AspNetCore.Server.Kestrel.Internal.System.IO.Pipelines;
namespace Microsoft.AspNetCore.Server.Kestrel.Core.Adapter.Internal
{
public class StreamSocketOutput : ISocketOutput
{
private readonly Stream _outputStream;
private readonly IPipe _pipe;
private readonly object _sync = new object();
private bool _completed;
public StreamSocketOutput(Stream outputStream, IPipe pipe)
{
_outputStream = outputStream;
_pipe = pipe;
}
public void Write(ArraySegment<byte> buffer, bool chunk)
{
WriteAsync(buffer, chunk, default(CancellationToken)).GetAwaiter().GetResult();
}
public async Task WriteAsync(ArraySegment<byte> buffer, bool chunk, CancellationToken cancellationToken)
{
var flushAwaiter = default(WritableBufferAwaitable);
lock (_sync)
{
if (_completed)
{
return;
}
var writableBuffer = _pipe.Writer.Alloc(1);
var writer = new WritableBufferWriter(writableBuffer);
if (buffer.Count > 0)
{
if (chunk)
{
ChunkWriter.WriteBeginChunkBytes(ref writer, buffer.Count);
writer.Write(buffer.Array, buffer.Offset, buffer.Count);
ChunkWriter.WriteEndChunkBytes(ref writer);
}
else
{
writer.Write(buffer.Array, buffer.Offset, buffer.Count);
}
}
flushAwaiter = writableBuffer.FlushAsync(cancellationToken);
}
await flushAwaiter;
}
public void Dispose()
{
lock (_sync)
{
_completed = true;
}
_pipe.Writer.Complete();
}
public void Flush()
{
FlushAsync(CancellationToken.None).GetAwaiter().GetResult();
}
public Task FlushAsync(CancellationToken cancellationToken)
{
return WriteAsync(default(ArraySegment<byte>), chunk: false, cancellationToken: cancellationToken);
}
public void Write<T>(Action<WritableBuffer, T> callback, T state) where T : struct
{
lock (_sync)
{
if (_completed)
{
return;
}
var buffer = _pipe.Writer.Alloc(1);
callback(buffer, state);
buffer.Commit();
}
}
public async Task WriteOutputAsync()
{
try
{
while (true)
{
var readResult = await _pipe.Reader.ReadAsync();
var buffer = readResult.Buffer;
try
{
if (buffer.IsEmpty && readResult.IsCompleted)
{
break;
}
if (buffer.IsEmpty)
{
await _outputStream.FlushAsync();
}
else if (buffer.IsSingleSpan)
{
var array = buffer.First.GetArray();
await _outputStream.WriteAsync(array.Array, array.Offset, array.Count);
}
else
{
foreach (var memory in buffer)
{
var array = memory.GetArray();
await _outputStream.WriteAsync(array.Array, array.Offset, array.Count);
}
}
}
finally
{
_pipe.Reader.Advance(buffer.End);
}
}
}
finally
{
_pipe.Reader.Complete();
}
}
}
}

View File

@ -40,10 +40,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal
ServiceContext = _serviceContext ServiceContext = _serviceContext
}; };
// TODO: Untangle this mess
var frame = new Frame<TContext>(_application, frameContext); var frame = new Frame<TContext>(_application, frameContext);
var outputProducer = new OutputProducer(outputPipe.Writer, frame, connectionId, _serviceContext.Log);
frame.LifetimeControl = new ConnectionLifetimeControl(connectionId, outputPipe.Reader, outputProducer, _serviceContext.Log);
var connection = new FrameConnection(new FrameConnectionContext var connection = new FrameConnection(new FrameConnectionContext
{ {
@ -55,7 +52,6 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal
Frame = frame, Frame = frame,
Input = inputPipe, Input = inputPipe,
Output = outputPipe, Output = outputPipe,
OutputProducer = outputProducer
}); });
_serviceContext.Log.ConnectionStart(connectionId); _serviceContext.Log.ConnectionStart(connectionId);

View File

@ -28,7 +28,6 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal
private TimeoutAction _timeoutAction; private TimeoutAction _timeoutAction;
private Task _lifetimeTask; private Task _lifetimeTask;
private Stream _filteredStream;
public FrameConnection(FrameConnectionContext context) public FrameConnection(FrameConnectionContext context)
{ {
@ -69,46 +68,39 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal
private async Task ProcessRequestsAsync() private async Task ProcessRequestsAsync()
{ {
RawStream rawStream = null;
try try
{ {
Task adaptedPipelineTask = Task.CompletedTask; AdaptedPipeline adaptedPipeline = null;
var adaptedPipelineTask = Task.CompletedTask;
var input = _context.Input.Reader;
var output = _context.Output.Writer;
if (_connectionAdapters.Count == 0) if (_connectionAdapters.Count > 0)
{ {
_frame.Input = _context.Input.Reader; adaptedPipeline = new AdaptedPipeline(_context.Input.Reader,
_frame.Output = _context.OutputProducer; _context.Output.Writer,
} PipeFactory.Create(AdaptedInputPipeOptions),
else PipeFactory.Create(AdaptedOutputPipeOptions),
{ Log);
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;
}
input = adaptedPipeline.Input.Reader;
output = adaptedPipeline.Output.Writer;
} }
// Set these before the first await, this is to make sure that we don't yield control
// to the transport until we've added the connection to the connection manager
_frame.TimeoutControl = this; _frame.TimeoutControl = this;
_lastTimestamp = _context.ServiceContext.SystemClock.UtcNow.Ticks; _frame.Input = input;
_frame.Output = new OutputProducer(output, ConnectionId, Log);
_context.ServiceContext.ConnectionManager.AddConnection(_context.FrameConnectionId, this); _context.ServiceContext.ConnectionManager.AddConnection(_context.FrameConnectionId, this);
_lastTimestamp = _context.ServiceContext.SystemClock.UtcNow.Ticks;
if (adaptedPipeline != null)
{
// Stream can be null here and run async will close the connection in that case
var stream = await ApplyConnectionAdaptersAsync();
adaptedPipelineTask = adaptedPipeline.RunAsync(stream);
}
await _frame.ProcessRequestsAsync(); await _frame.ProcessRequestsAsync();
await adaptedPipelineTask; await adaptedPipelineTask;
@ -121,14 +113,13 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal
finally finally
{ {
_context.ServiceContext.ConnectionManager.RemoveConnection(_context.FrameConnectionId); _context.ServiceContext.ConnectionManager.RemoveConnection(_context.FrameConnectionId);
rawStream?.Dispose();
DisposeAdaptedConnections(); DisposeAdaptedConnections();
} }
} }
public void OnConnectionClosed(Exception ex) public void OnConnectionClosed(Exception ex)
{ {
// Abort the connection (if it isn't already aborted) // Abort the connection (if not already aborted)
_frame.Abort(ex); _frame.Abort(ex);
Log.ConnectionStop(ConnectionId); Log.ConnectionStop(ConnectionId);
@ -139,17 +130,21 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal
public Task StopAsync() public Task StopAsync()
{ {
_frame.Stop(); _frame.Stop();
return _lifetimeTask; return _lifetimeTask;
} }
public void Abort(Exception ex) public void Abort(Exception ex)
{ {
// Abort the connection (if not already aborted)
_frame.Abort(ex); _frame.Abort(ex);
} }
public Task AbortAsync(Exception ex) public Task AbortAsync(Exception ex)
{ {
// Abort the connection (if not already aborted)
_frame.Abort(ex); _frame.Abort(ex);
return _lifetimeTask; return _lifetimeTask;
} }
@ -158,25 +153,33 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal
_frame.SetBadRequestState(RequestRejectionReason.RequestTimeout); _frame.SetBadRequestState(RequestRejectionReason.RequestTimeout);
} }
private async Task<AdaptedPipeline> ApplyConnectionAdaptersAsync(RawStream rawStream) private async Task<Stream> ApplyConnectionAdaptersAsync()
{ {
var adapterContext = new ConnectionAdapterContext(rawStream); var stream = new RawStream(_context.Input.Reader, _context.Output.Writer);
var adapterContext = new ConnectionAdapterContext(stream);
var adaptedConnections = new IAdaptedConnection[_connectionAdapters.Count]; var adaptedConnections = new IAdaptedConnection[_connectionAdapters.Count];
for (var i = 0; i < _connectionAdapters.Count; i++) try
{ {
var adaptedConnection = await _connectionAdapters[i].OnConnectionAsync(adapterContext); for (var i = 0; i < _connectionAdapters.Count; i++)
adaptedConnections[i] = adaptedConnection; {
adapterContext = new ConnectionAdapterContext(adaptedConnection.ConnectionStream); var adaptedConnection = await _connectionAdapters[i].OnConnectionAsync(adapterContext);
adaptedConnections[i] = adaptedConnection;
adapterContext = new ConnectionAdapterContext(adaptedConnection.ConnectionStream);
}
}
catch (Exception ex)
{
Log.LogError(0, ex, $"Uncaught exception from the {nameof(IConnectionAdapter.OnConnectionAsync)} method of an {nameof(IConnectionAdapter)}.");
return null;
}
finally
{
_frame.AdaptedConnections = adaptedConnections;
} }
_filteredStream = adapterContext.ConnectionStream; return adapterContext.ConnectionStream;
_frame.AdaptedConnections = adaptedConnections;
return new AdaptedPipeline(_filteredStream,
PipeFactory.Create(AdaptedInputPipeOptions),
PipeFactory.Create(AdaptedOutputPipeOptions),
Log);
} }
private void DisposeAdaptedConnections() private void DisposeAdaptedConnections()

View File

@ -16,7 +16,6 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal
public PipeFactory PipeFactory { get; set; } public PipeFactory PipeFactory { get; set; }
public List<IConnectionAdapter> ConnectionAdapters { get; set; } public List<IConnectionAdapter> ConnectionAdapters { get; set; }
public Frame Frame { get; set; } public Frame Frame { get; set; }
public OutputProducer OutputProducer { get; set; }
public IPipe Input { get; set; } public IPipe Input { get; set; }
public IPipe Output { get; set; } public IPipe Output { get; set; }

View File

@ -1,45 +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 Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Infrastructure;
using Microsoft.AspNetCore.Server.Kestrel.Internal.System.IO.Pipelines;
namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http
{
public class ConnectionLifetimeControl
{
public ConnectionLifetimeControl(
string connectionId,
IPipeReader outputPipeReader,
OutputProducer outputProducer,
IKestrelTrace log)
{
ConnectionId = connectionId;
OutputReader = outputPipeReader;
OutputProducer = outputProducer;
Log = log;
}
private string ConnectionId { get; }
private IPipeReader OutputReader { get; }
private OutputProducer OutputProducer { get; }
private IKestrelTrace Log { get; }
public void End(ProduceEndType endType)
{
switch (endType)
{
case ProduceEndType.ConnectionKeepAlive:
Log.ConnectionKeepAlive(ConnectionId);
break;
case ProduceEndType.SocketShutdown:
OutputReader.CancelPendingRead();
goto case ProduceEndType.SocketDisconnect;
case ProduceEndType.SocketDisconnect:
OutputProducer.Dispose();
Log.ConnectionDisconnect(ConnectionId);
break;
}
}
}
}

View File

@ -102,9 +102,8 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http
public IConnectionInformation ConnectionInformation => _frameContext.ConnectionInformation; public IConnectionInformation ConnectionInformation => _frameContext.ConnectionInformation;
public IPipeReader Input { get; set; } public IPipeReader Input { get; set; }
public ISocketOutput Output { get; set; } public OutputProducer Output { get; set; }
public IAdaptedConnection[] AdaptedConnections { get; set; } public IAdaptedConnection[] AdaptedConnections { get; set; }
public ConnectionLifetimeControl LifetimeControl { get; set; }
public ITimeoutControl TimeoutControl { get; set; } public ITimeoutControl TimeoutControl { get; set; }
protected IKestrelTrace Log => ServiceContext.Log; protected IKestrelTrace Log => ServiceContext.Log;
@ -411,7 +410,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http
_frameStreams?.Abort(error); _frameStreams?.Abort(error);
LifetimeControl.End(ProduceEndType.SocketDisconnect); Output.Abort();
// Potentially calling user code. CancelRequestAbortedToken logs any exceptions. // Potentially calling user code. CancelRequestAbortedToken logs any exceptions.
ServiceContext.ThreadPool.UnsafeRun(state => ((Frame)state).CancelRequestAbortedToken(), this); ServiceContext.ThreadPool.UnsafeRun(state => ((Frame)state).CancelRequestAbortedToken(), this);
@ -827,7 +826,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http
if (_keepAlive) if (_keepAlive)
{ {
LifetimeControl.End(ProduceEndType.ConnectionKeepAlive); Log.ConnectionKeepAlive(ConnectionId);
} }
if (HttpMethods.IsHead(Method) && _responseBytesWritten > 0) if (HttpMethods.IsHead(Method) && _responseBytesWritten > 0)
@ -847,7 +846,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http
if (_keepAlive) if (_keepAlive)
{ {
LifetimeControl.End(ProduceEndType.ConnectionKeepAlive); Log.ConnectionKeepAlive(ConnectionId);
} }
} }

View File

@ -229,7 +229,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http
if (Volatile.Read(ref _requestAborted) == 0) if (Volatile.Read(ref _requestAborted) == 0)
{ {
await TryProduceInvalidRequestResponse(); await TryProduceInvalidRequestResponse();
LifetimeControl.End(ProduceEndType.SocketShutdown); Output.Dispose();
} }
} }
catch (Exception ex) catch (Exception ex)

View File

@ -1,22 +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;
using System.Threading;
using System.Threading.Tasks;
using Microsoft.AspNetCore.Server.Kestrel.Internal.System.IO.Pipelines;
namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http
{
/// <summary>
/// Operations performed for buffered socket output
/// </summary>
public interface ISocketOutput
{
void Write(ArraySegment<byte> buffer, bool chunk = false);
Task WriteAsync(ArraySegment<byte> buffer, bool chunk = false, CancellationToken cancellationToken = default(CancellationToken));
void Flush();
Task FlushAsync(CancellationToken cancellationToken = default(CancellationToken));
void Write<T>(Action<WritableBuffer, T> write, T state) where T : struct;
}
}

View File

@ -6,11 +6,12 @@ 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
{ {
public class OutputProducer : ISocketOutput, IDisposable public class OutputProducer : IDisposable
{ {
private static readonly ArraySegment<byte> _emptyData = new ArraySegment<byte>(new byte[0]); private static readonly ArraySegment<byte> _emptyData = new ArraySegment<byte>(new byte[0]);
@ -24,7 +25,6 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http
private bool _completed = false; private bool _completed = false;
private readonly IPipeWriter _pipe; private readonly IPipeWriter _pipe;
private readonly Frame _frame;
// 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,16 +33,90 @@ 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, Frame frame, string connectionId, IKestrelTrace log) public OutputProducer(IPipeWriter pipe, string connectionId, IKestrelTrace log)
{ {
_pipe = pipe; _pipe = pipe;
_frame = frame;
_connectionId = connectionId; _connectionId = connectionId;
_log = log; _log = log;
_flushCompleted = OnFlushCompleted; _flushCompleted = OnFlushCompleted;
} }
public Task WriteAsync( public void Write(ArraySegment<byte> buffer, bool chunk = false)
{
WriteAsync(buffer, default(CancellationToken), chunk).GetAwaiter().GetResult();
}
public Task WriteAsync(ArraySegment<byte> buffer, bool chunk = false, CancellationToken cancellationToken = default(CancellationToken))
{
if (cancellationToken.IsCancellationRequested)
{
_cancelled = true;
return Task.FromCanceled(cancellationToken);
}
else if (_cancelled)
{
return TaskCache.CompletedTask;
}
return WriteAsync(buffer, cancellationToken, chunk);
}
public void Flush()
{
WriteAsync(_emptyData, default(CancellationToken)).GetAwaiter().GetResult();
}
public Task FlushAsync(CancellationToken cancellationToken = default(CancellationToken))
{
return WriteAsync(_emptyData, cancellationToken);
}
public void Write<T>(Action<WritableBuffer, T> callback, T state)
{
lock (_contextLock)
{
if (_completed)
{
return;
}
var buffer = _pipe.Alloc(1);
callback(buffer, state);
buffer.Commit();
}
}
public void Dispose()
{
lock (_contextLock)
{
if (_completed)
{
return;
}
_log.ConnectionDisconnect(_connectionId);
_completed = true;
_pipe.Complete();
}
}
public void Abort()
{
lock (_contextLock)
{
if (_completed)
{
return;
}
_log.ConnectionDisconnect(_connectionId);
_completed = true;
_pipe.Complete(new ConnectionAbortedException());
}
}
private Task WriteAsync(
ArraySegment<byte> buffer, ArraySegment<byte> buffer,
CancellationToken cancellationToken, CancellationToken cancellationToken,
bool chunk = false) bool chunk = false)
@ -108,11 +182,6 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http
} }
await _flushTcs.Task; await _flushTcs.Task;
if (cancellationToken.IsCancellationRequested)
{
_frame.Abort(error: null);
}
cancellationToken.ThrowIfCancellationRequested(); cancellationToken.ThrowIfCancellationRequested();
} }
@ -120,60 +189,5 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http
{ {
_flushTcs.TrySetResult(null); _flushTcs.TrySetResult(null);
} }
void ISocketOutput.Write(ArraySegment<byte> buffer, bool chunk)
{
WriteAsync(buffer, default(CancellationToken), chunk).GetAwaiter().GetResult();
}
Task ISocketOutput.WriteAsync(ArraySegment<byte> buffer, bool chunk, CancellationToken cancellationToken)
{
if (cancellationToken.IsCancellationRequested)
{
_frame.Abort(error: null);
_cancelled = true;
return Task.FromCanceled(cancellationToken);
}
else if (_cancelled)
{
return TaskCache.CompletedTask;
}
return WriteAsync(buffer, cancellationToken, chunk);
}
void ISocketOutput.Flush()
{
WriteAsync(_emptyData, default(CancellationToken)).GetAwaiter().GetResult();
}
Task ISocketOutput.FlushAsync(CancellationToken cancellationToken)
{
return WriteAsync(_emptyData, cancellationToken);
}
void ISocketOutput.Write<T>(Action<WritableBuffer, T> callback, T state)
{
lock (_contextLock)
{
if (_completed)
{
return;
}
var buffer = _pipe.Alloc(1);
callback(buffer, state);
buffer.Commit();
}
}
public void Dispose()
{
lock (_contextLock)
{
_completed = true;
_pipe.Complete();
}
}
} }
} }

View File

@ -4,6 +4,7 @@
using System; using System;
using System.Threading.Tasks; using System.Threading.Tasks;
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.AspNetCore.Server.Kestrel.Transport.Libuv.Internal.Networking; using Microsoft.AspNetCore.Server.Kestrel.Transport.Libuv.Internal.Networking;
namespace Microsoft.AspNetCore.Server.Kestrel.Transport.Libuv.Internal namespace Microsoft.AspNetCore.Server.Kestrel.Transport.Libuv.Internal
@ -36,48 +37,50 @@ 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
{ {
if (!buffer.IsEmpty) var result = await _pipe.ReadAsync();
var buffer = result.Buffer;
var consumed = buffer.End;
try
{ {
var writeReq = pool.Allocate(); if (!buffer.IsEmpty)
try
{ {
var writeResult = await writeReq.WriteAsync(_socket, buffer); var writeReq = pool.Allocate();
LogWriteInfo(writeResult.Status, writeResult.Error); try
if (writeResult.Error != null)
{ {
consumed = buffer.Start; var writeResult = await writeReq.WriteAsync(_socket, buffer);
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)
{ {
// Make sure we return the writeReq to the pool break;
pool.Return(writeReq);
} }
} }
finally
if (result.IsCancelled)
{ {
break; _pipe.Advance(consumed);
}
if (buffer.IsEmpty && result.IsCompleted)
{
break;
} }
} }
finally catch (ConnectionAbortedException)
{ {
_pipe.Advance(consumed); break;
} }
} }
} }

View File

@ -216,6 +216,10 @@ 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

@ -54,6 +54,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Tests
{ {
_pipelineFactory = new PipeFactory(); _pipelineFactory = new PipeFactory();
_input = _pipelineFactory.Create(); _input = _pipelineFactory.Create();
var output = _pipelineFactory.Create();
_serviceContext = new TestServiceContext(); _serviceContext = new TestServiceContext();
@ -66,10 +67,11 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Tests
_frame = new TestFrame<object>(application: null, context: _frameContext) _frame = new TestFrame<object>(application: null, context: _frameContext)
{ {
Input = _input.Reader, Input = _input.Reader,
Output = new MockSocketOutput(),
TimeoutControl = Mock.Of<ITimeoutControl>() TimeoutControl = Mock.Of<ITimeoutControl>()
}; };
_frame.Output = new OutputProducer(output.Writer, "", Mock.Of<IKestrelTrace>());
_frame.Reset(); _frame.Reset();
} }

View File

@ -38,7 +38,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Tests
var called = false; var called = false;
((ISocketOutput)socketOutput).Write((buffer, state) => socketOutput.Write((buffer, state) =>
{ {
called = true; called = true;
}, },
@ -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, frame, "0", serviceContext.Log); var socketOutput = new OutputProducer(pipe.Writer, "0", serviceContext.Log);
return socketOutput; return socketOutput;
} }

View File

@ -1,110 +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;
using System.IO;
using Microsoft.AspNetCore.Server.Kestrel.Core.Adapter.Internal;
using Microsoft.AspNetCore.Server.Kestrel.Internal.System.IO.Pipelines;
using Xunit;
namespace Microsoft.AspNetCore.Server.Kestrel.Core.Tests
{
public class StreamSocketOutputTests
{
[Fact]
public void DoesNotThrowForNullBuffers()
{
// This test was added because SslStream throws if passed null buffers with (count == 0)
// Which happens if ProduceEnd is called in Frame without _responseStarted == true
// As it calls ProduceStart with write immediate == true
// This happens in WebSocket Upgrade over SSL
using (var factory = new PipeFactory())
{
var socketOutput = new StreamSocketOutput(new ThrowsOnNullWriteStream(), factory.Create());
// Should not throw
socketOutput.Write(default(ArraySegment<byte>), true);
Assert.True(true);
socketOutput.Dispose();
}
}
private class ThrowsOnNullWriteStream : Stream
{
public override bool CanRead
{
get
{
throw new NotImplementedException();
}
}
public override bool CanSeek
{
get
{
throw new NotImplementedException();
}
}
public override bool CanWrite
{
get
{
throw new NotImplementedException();
}
}
public override long Length
{
get
{
throw new NotImplementedException();
}
}
public override long Position
{
get
{
throw new NotImplementedException();
}
set
{
throw new NotImplementedException();
}
}
public override void Flush()
{
throw new NotImplementedException();
}
public override int Read(byte[] buffer, int offset, int count)
{
throw new NotImplementedException();
}
public override long Seek(long offset, SeekOrigin origin)
{
throw new NotImplementedException();
}
public override void SetLength(long value)
{
throw new NotImplementedException();
}
public override void Write(byte[] buffer, int offset, int count)
{
if (buffer == null)
{
throw new ArgumentNullException(nameof(buffer));
}
}
}
}
}

View File

@ -4,6 +4,7 @@
using System; using System;
using System.IO; using System.IO;
using System.Net; using System.Net;
using System.Net.Sockets;
using System.Threading; using System.Threading;
using System.Threading.Tasks; using System.Threading.Tasks;
using Microsoft.AspNetCore.Http.Features; using Microsoft.AspNetCore.Http.Features;
@ -76,6 +77,70 @@ namespace Microsoft.AspNetCore.Server.Kestrel.FunctionalTests
} }
} }
[Fact]
public async Task ImmediateFinAfterOnConnectionAsyncClosesGracefully()
{
var listenOptions = new ListenOptions(new IPEndPoint(IPAddress.Loopback, 0))
{
ConnectionAdapters = { new AsyncConnectionAdapter() }
};
var serviceContext = new TestServiceContext();
using (var server = new TestServer(TestApp.EchoApp, serviceContext, listenOptions))
{
using (var connection = server.CreateConnection())
{
// FIN
connection.Shutdown(SocketShutdown.Send);
await connection.WaitForConnectionClose();
}
}
}
[Fact]
public async Task ImmediateFinAfterThrowingClosesGracefully()
{
var listenOptions = new ListenOptions(new IPEndPoint(IPAddress.Loopback, 0))
{
ConnectionAdapters = { new ThrowingConnectionAdapter() }
};
var serviceContext = new TestServiceContext();
using (var server = new TestServer(TestApp.EchoApp, serviceContext, listenOptions))
{
using (var connection = server.CreateConnection())
{
// FIN
connection.Shutdown(SocketShutdown.Send);
await connection.WaitForConnectionClose();
}
}
}
[Fact]
public async Task ImmediateShutdownAfterOnConnectionAsyncDoesNotCrash()
{
var listenOptions = new ListenOptions(new IPEndPoint(IPAddress.Loopback, 0))
{
ConnectionAdapters = { new AsyncConnectionAdapter() }
};
var serviceContext = new TestServiceContext();
var stopTask = Task.CompletedTask;
using (var server = new TestServer(TestApp.EchoApp, serviceContext, listenOptions))
{
using (var connection = server.CreateConnection())
{
stopTask = server.StopAsync();
}
await stopTask;
}
}
[Fact] [Fact]
public async Task ThrowingSynchronousConnectionAdapterDoesNotCrashServer() public async Task ThrowingSynchronousConnectionAdapterDoesNotCrashServer()
{ {
@ -121,7 +186,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.FunctionalTests
} }
public int BytesRead => _rewritingStream.BytesRead; public int BytesRead => _rewritingStream.BytesRead;
} }
private class AsyncConnectionAdapter : IConnectionAdapter private class AsyncConnectionAdapter : IConnectionAdapter
{ {

View File

@ -1051,24 +1051,28 @@ namespace Microsoft.AspNetCore.Server.Kestrel.FunctionalTests
{ {
using (var connection = server.CreateConnection()) using (var connection = server.CreateConnection())
{ {
// Never send the body so CopyToAsync always fails. // Full request and response
await connection.Send( await connection.Send(
"POST / HTTP/1.1", "POST / HTTP/1.1",
"Host:", "Host:",
"Content-Length: 5", "Content-Length: 5",
"", "",
"HelloPOST / HTTP/1.1", "Hello");
"Host:",
"Content-Length: 5",
"",
"");
await connection.ReceiveForcedEnd( await connection.Receive(
"HTTP/1.1 200 OK", "HTTP/1.1 200 OK",
$"Date: {testContext.DateHeaderValue}", $"Date: {testContext.DateHeaderValue}",
"Content-Length: 5", "Content-Length: 5",
"", "",
"World"); "World");
// Never send the body so CopyToAsync always fails.
await connection.Send("POST / HTTP/1.1",
"Host:",
"Content-Length: 5",
"",
"");
await connection.WaitForConnectionClose();
} }
} }

View File

@ -5,6 +5,7 @@ using System;
using System.Net; using System.Net;
using System.Net.Sockets; using System.Net.Sockets;
using System.Reflection; using System.Reflection;
using System.Threading.Tasks;
using Microsoft.AspNetCore.Builder; using Microsoft.AspNetCore.Builder;
using Microsoft.AspNetCore.Hosting; using Microsoft.AspNetCore.Hosting;
using Microsoft.AspNetCore.Hosting.Server; using Microsoft.AspNetCore.Hosting.Server;
@ -108,6 +109,11 @@ namespace Microsoft.AspNetCore.Server.Kestrel.FunctionalTests
return new TestConnection(Port, AddressFamily); return new TestConnection(Port, AddressFamily);
} }
public Task StopAsync()
{
return _host.StopAsync();
}
public void Dispose() public void Dispose()
{ {
_host.Dispose(); _host.Dispose();

View File

@ -87,7 +87,9 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Performance
private TestFrame<object> MakeFrame() private TestFrame<object> MakeFrame()
{ {
var socketInput = new PipeFactory().Create(); var factory = new PipeFactory();
var input = factory.Create();
var output = factory.Create();
var serviceContext = new ServiceContext var serviceContext = new ServiceContext
{ {
@ -104,10 +106,11 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Performance
var frame = new TestFrame<object>(application: null, context: frameContext) var frame = new TestFrame<object>(application: null, context: frameContext)
{ {
Input = socketInput.Reader, Input = input.Reader,
Output = new MockSocketOutput()
}; };
frame.Output = new OutputProducer(output.Writer, "", null);
frame.Reset(); frame.Reset();
return frame; return frame;

View File

@ -1,4 +1,4 @@
<Project Sdk="Microsoft.NET.Sdk"> <Project Sdk="Microsoft.NET.Sdk">
<Import Project="..\..\build\common.props" /> <Import Project="..\..\build\common.props" />
@ -14,7 +14,6 @@
<Compile Include="..\shared\TestApplicationErrorLogger.cs" /> <Compile Include="..\shared\TestApplicationErrorLogger.cs" />
<Compile Include="..\shared\TestFrame.cs" /> <Compile Include="..\shared\TestFrame.cs" />
<Compile Include="..\shared\TestKestrelTrace.cs" /> <Compile Include="..\shared\TestKestrelTrace.cs" />
<Compile Include="..\shared\MockSocketOutput.cs" />
</ItemGroup> </ItemGroup>
<ItemGroup> <ItemGroup>

View File

@ -8,7 +8,6 @@ using System.Threading;
using System.Threading.Tasks; using System.Threading.Tasks;
using BenchmarkDotNet.Attributes; using BenchmarkDotNet.Attributes;
using Microsoft.AspNetCore.Server.Kestrel.Core; using Microsoft.AspNetCore.Server.Kestrel.Core;
using Microsoft.AspNetCore.Server.Kestrel.Core.Adapter.Internal;
using Microsoft.AspNetCore.Server.Kestrel.Core.Internal; using Microsoft.AspNetCore.Server.Kestrel.Core.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;
@ -113,7 +112,6 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Performance
var factory = new PipeFactory(); var factory = new PipeFactory();
var input = factory.Create(); var input = factory.Create();
var output = factory.Create(); var output = factory.Create();
var socketOutput = new StreamSocketOutput(Stream.Null, output);
var serviceContext = new ServiceContext var serviceContext = new ServiceContext
{ {
@ -129,19 +127,14 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Performance
ConnectionInformation = new MockConnectionInformation() ConnectionInformation = new MockConnectionInformation()
}; };
var outputProducer = new OutputProducer(output.Writer, null, null, null);
var frame = new TestFrame<object>(application: null, context: frameContext) var frame = new TestFrame<object>(application: null, context: frameContext)
{ {
Input = input.Reader, Input = input.Reader,
Output = socketOutput,
LifetimeControl = new ConnectionLifetimeControl(null, output.Reader, outputProducer, serviceContext.Log)
}; };
frame.Output = new OutputProducer(output.Writer, "", null);
frame.Reset(); frame.Reset();
// Start writing
var ignore = socketOutput.WriteOutputAsync();
_frame = frame; _frame = frame;
} }

View File

@ -9,7 +9,6 @@ using Microsoft.AspNetCore.Server.Kestrel.Core;
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;
using Microsoft.AspNetCore.Server.Kestrel.Transport.Libuv.Internal; using Microsoft.AspNetCore.Server.Kestrel.Transport.Libuv.Internal;
using Microsoft.AspNetCore.Server.Kestrel.Transport.Libuv.Internal.Networking; using Microsoft.AspNetCore.Server.Kestrel.Transport.Libuv.Internal.Networking;
using Microsoft.AspNetCore.Server.Kestrel.Transport.Libuv.Tests.TestHelpers; using Microsoft.AspNetCore.Server.Kestrel.Transport.Libuv.Tests.TestHelpers;
@ -75,7 +74,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Transport.Libuv.Tests
var buffer = new ArraySegment<byte>(new byte[bufferSize], 0, bufferSize); var buffer = new ArraySegment<byte>(new byte[bufferSize], 0, bufferSize);
// Act // Act
var writeTask = socketOutput.WriteAsync(buffer, default(CancellationToken)); var writeTask = socketOutput.WriteAsync(buffer);
// Assert // Assert
await writeTask.TimeoutAfter(TimeSpan.FromSeconds(5)); await writeTask.TimeoutAfter(TimeSpan.FromSeconds(5));
@ -110,7 +109,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Transport.Libuv.Tests
var buffer = new ArraySegment<byte>(new byte[bufferSize], 0, bufferSize); var buffer = new ArraySegment<byte>(new byte[bufferSize], 0, bufferSize);
// Act // Act
var writeTask = socketOutput.WriteAsync(buffer, default(CancellationToken)); var writeTask = socketOutput.WriteAsync(buffer);
// Assert // Assert
await writeTask.TimeoutAfter(TimeSpan.FromSeconds(5)); await writeTask.TimeoutAfter(TimeSpan.FromSeconds(5));
@ -156,7 +155,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Transport.Libuv.Tests
var buffer = new ArraySegment<byte>(new byte[bufferSize], 0, bufferSize); var buffer = new ArraySegment<byte>(new byte[bufferSize], 0, bufferSize);
// Act // Act
var writeTask = socketOutput.WriteAsync(buffer, default(CancellationToken)); var writeTask = socketOutput.WriteAsync(buffer);
// Assert // Assert
Assert.False(writeTask.IsCompleted); Assert.False(writeTask.IsCompleted);
@ -211,14 +210,14 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Transport.Libuv.Tests
var buffer = new ArraySegment<byte>(new byte[bufferSize], 0, bufferSize); var buffer = new ArraySegment<byte>(new byte[bufferSize], 0, bufferSize);
// Act // Act
var writeTask1 = socketOutput.WriteAsync(buffer, default(CancellationToken)); var writeTask1 = socketOutput.WriteAsync(buffer);
// Assert // Assert
// The first write should pre-complete since it is <= _maxBytesPreCompleted. // The first write should pre-complete since it is <= _maxBytesPreCompleted.
Assert.Equal(TaskStatus.RanToCompletion, writeTask1.Status); Assert.Equal(TaskStatus.RanToCompletion, writeTask1.Status);
// Act // Act
var writeTask2 = socketOutput.WriteAsync(buffer, default(CancellationToken)); var writeTask2 = socketOutput.WriteAsync(buffer);
await _mockLibuv.OnPostTask; await _mockLibuv.OnPostTask;
// Assert // Assert
@ -275,7 +274,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Transport.Libuv.Tests
var halfWriteBehindBuffer = new ArraySegment<byte>(data, 0, bufferSize); var halfWriteBehindBuffer = new ArraySegment<byte>(data, 0, bufferSize);
// Act // Act
var writeTask1 = socketOutput.WriteAsync(halfWriteBehindBuffer, default(CancellationToken)); var writeTask1 = socketOutput.WriteAsync(halfWriteBehindBuffer);
// Assert // Assert
// The first write should pre-complete since it is <= _maxBytesPreCompleted. // The first write should pre-complete since it is <= _maxBytesPreCompleted.
@ -284,17 +283,17 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Transport.Libuv.Tests
Assert.NotEmpty(completeQueue); Assert.NotEmpty(completeQueue);
// Add more bytes to the write-behind buffer to prevent the next write from // Add more bytes to the write-behind buffer to prevent the next write from
((ISocketOutput)socketOutput).Write((writableBuffer, state) => socketOutput.Write((writableBuffer, state) =>
{ {
writableBuffer.Write(state); writableBuffer.Write(state);
}, },
halfWriteBehindBuffer); halfWriteBehindBuffer);
// Act // Act
var writeTask2 = socketOutput.WriteAsync(halfWriteBehindBuffer, default(CancellationToken)); var writeTask2 = socketOutput.WriteAsync(halfWriteBehindBuffer);
Assert.False(writeTask2.IsCompleted); Assert.False(writeTask2.IsCompleted);
var writeTask3 = socketOutput.WriteAsync(halfWriteBehindBuffer, default(CancellationToken)); var writeTask3 = socketOutput.WriteAsync(halfWriteBehindBuffer);
Assert.False(writeTask3.IsCompleted); Assert.False(writeTask3.IsCompleted);
// Drain the write queue // Drain the write queue
@ -345,7 +344,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Transport.Libuv.Tests
var fullBuffer = new ArraySegment<byte>(data, 0, bufferSize); var fullBuffer = new ArraySegment<byte>(data, 0, bufferSize);
// Act // Act
var task1Success = socketOutput.WriteAsync(fullBuffer, abortedSource.Token); var task1Success = socketOutput.WriteAsync(fullBuffer, cancellationToken: abortedSource.Token);
// task1 should complete successfully as < _maxBytesPreCompleted // task1 should complete successfully as < _maxBytesPreCompleted
// First task is completed and successful // First task is completed and successful
@ -354,8 +353,8 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Transport.Libuv.Tests
Assert.False(task1Success.IsFaulted); Assert.False(task1Success.IsFaulted);
// following tasks should wait. // following tasks should wait.
var task2Success = socketOutput.WriteAsync(fullBuffer, default(CancellationToken)); var task2Success = socketOutput.WriteAsync(fullBuffer);
var task3Canceled = socketOutput.WriteAsync(fullBuffer, abortedSource.Token); var task3Canceled = socketOutput.WriteAsync(fullBuffer, cancellationToken: abortedSource.Token);
// Give time for tasks to percolate // Give time for tasks to percolate
await _mockLibuv.OnPostTask; await _mockLibuv.OnPostTask;
@ -383,7 +382,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Transport.Libuv.Tests
// A final write guarantees that the error is observed by OutputProducer, // A final write guarantees that the error is observed by OutputProducer,
// but doesn't return a canceled/faulted task. // but doesn't return a canceled/faulted task.
var task4Success = socketOutput.WriteAsync(fullBuffer, default(CancellationToken)); var task4Success = socketOutput.WriteAsync(fullBuffer, cancellationToken: default(CancellationToken));
Assert.True(task4Success.IsCompleted); Assert.True(task4Success.IsCompleted);
Assert.False(task4Success.IsCanceled); Assert.False(task4Success.IsCanceled);
Assert.False(task4Success.IsFaulted); Assert.False(task4Success.IsFaulted);
@ -429,7 +428,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Transport.Libuv.Tests
var fullBuffer = new ArraySegment<byte>(data, 0, bufferSize); var fullBuffer = new ArraySegment<byte>(data, 0, bufferSize);
// Act // Act
var task1Success = socketOutput.WriteAsync(fullBuffer, abortedSource.Token); var task1Success = socketOutput.WriteAsync(fullBuffer, cancellationToken: abortedSource.Token);
// task1 should complete successfully as < _maxBytesPreCompleted // task1 should complete successfully as < _maxBytesPreCompleted
// First task is completed and successful // First task is completed and successful
@ -438,7 +437,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Transport.Libuv.Tests
Assert.False(task1Success.IsFaulted); Assert.False(task1Success.IsFaulted);
// following tasks should wait. // following tasks should wait.
var task3Canceled = socketOutput.WriteAsync(fullBuffer, abortedSource.Token); var task3Canceled = socketOutput.WriteAsync(fullBuffer, cancellationToken: abortedSource.Token);
// Give time for tasks to percolate // Give time for tasks to percolate
await _mockLibuv.OnPostTask; await _mockLibuv.OnPostTask;
@ -458,7 +457,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Transport.Libuv.Tests
// A final write guarantees that the error is observed by OutputProducer, // A final write guarantees that the error is observed by OutputProducer,
// but doesn't return a canceled/faulted task. // but doesn't return a canceled/faulted task.
var task4Success = socketOutput.WriteAsync(fullBuffer, default(CancellationToken)); var task4Success = socketOutput.WriteAsync(fullBuffer);
Assert.True(task4Success.IsCompleted); Assert.True(task4Success.IsCompleted);
Assert.False(task4Success.IsCanceled); Assert.False(task4Success.IsCanceled);
Assert.False(task4Success.IsFaulted); Assert.False(task4Success.IsFaulted);
@ -504,7 +503,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Transport.Libuv.Tests
var fullBuffer = new ArraySegment<byte>(data, 0, bufferSize); var fullBuffer = new ArraySegment<byte>(data, 0, bufferSize);
// Act // Act
var task1Waits = socketOutput.WriteAsync(fullBuffer, default(CancellationToken)); var task1Waits = socketOutput.WriteAsync(fullBuffer);
// First task is not completed // First task is not completed
Assert.False(task1Waits.IsCompleted); Assert.False(task1Waits.IsCompleted);
@ -512,7 +511,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Transport.Libuv.Tests
Assert.False(task1Waits.IsFaulted); Assert.False(task1Waits.IsFaulted);
// following tasks should wait. // following tasks should wait.
var task3Canceled = socketOutput.WriteAsync(fullBuffer, abortedSource.Token); var task3Canceled = socketOutput.WriteAsync(fullBuffer, cancellationToken: abortedSource.Token);
// Give time for tasks to percolate // Give time for tasks to percolate
await _mockLibuv.OnPostTask; await _mockLibuv.OnPostTask;
@ -537,7 +536,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Transport.Libuv.Tests
// A final write guarantees that the error is observed by OutputProducer, // A final write guarantees that the error is observed by OutputProducer,
// but doesn't return a canceled/faulted task. // but doesn't return a canceled/faulted task.
var task4Success = socketOutput.WriteAsync(fullBuffer, default(CancellationToken)); var task4Success = socketOutput.WriteAsync(fullBuffer);
Assert.True(task4Success.IsCompleted); Assert.True(task4Success.IsCompleted);
Assert.False(task4Success.IsCanceled); Assert.False(task4Success.IsCanceled);
Assert.False(task4Success.IsFaulted); Assert.False(task4Success.IsFaulted);
@ -575,7 +574,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Transport.Libuv.Tests
var buffer = new ArraySegment<byte>(new byte[bufferSize], 0, bufferSize); var buffer = new ArraySegment<byte>(new byte[bufferSize], 0, bufferSize);
// Act (Pre-complete the maximum number of bytes in preparation for the rest of the test) // Act (Pre-complete the maximum number of bytes in preparation for the rest of the test)
var writeTask1 = socketOutput.WriteAsync(buffer, default(CancellationToken)); var writeTask1 = socketOutput.WriteAsync(buffer);
// Assert // Assert
// The first write should pre-complete since it is < _maxBytesPreCompleted. // The first write should pre-complete since it is < _maxBytesPreCompleted.
@ -584,8 +583,8 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Transport.Libuv.Tests
Assert.NotEmpty(completeQueue); Assert.NotEmpty(completeQueue);
// Act // Act
var writeTask2 = socketOutput.WriteAsync(buffer, default(CancellationToken)); var writeTask2 = socketOutput.WriteAsync(buffer);
var writeTask3 = socketOutput.WriteAsync(buffer, default(CancellationToken)); var writeTask3 = socketOutput.WriteAsync(buffer);
// Drain the write queue // Drain the write queue
while (completeQueue.TryDequeue(out var triggerNextCompleted)) while (completeQueue.TryDequeue(out var triggerNextCompleted))
@ -635,8 +634,8 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Transport.Libuv.Tests
// Two calls to WriteAsync trigger uv_write once if both calls // Two calls to WriteAsync trigger uv_write once if both calls
// are made before write is scheduled // are made before write is scheduled
var ignore = socketOutput.WriteAsync(buffer, CancellationToken.None); var ignore = socketOutput.WriteAsync(buffer);
ignore = socketOutput.WriteAsync(buffer, CancellationToken.None); ignore = socketOutput.WriteAsync(buffer);
_mockLibuv.KestrelThreadBlocker.Set(); _mockLibuv.KestrelThreadBlocker.Set();
@ -671,10 +670,9 @@ 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 socketOutput = new OutputProducer(pipe.Writer, frame, "0", serviceContext.Log); var outputProducer = new OutputProducer(pipe.Writer, "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.LifetimeControl = new ConnectionLifetimeControl("0", pipe.Reader, socketOutput, serviceContext.Log);
if (cts != null) if (cts != null)
{ {
@ -683,7 +681,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Transport.Libuv.Tests
var ignore = WriteOutputAsync(consumer, pipe.Reader, frame); var ignore = WriteOutputAsync(consumer, pipe.Reader, frame);
return socketOutput; return outputProducer;
} }
private async Task WriteOutputAsync(LibuvOutputConsumer consumer, IPipeReader outputReader, Frame frame) private async Task WriteOutputAsync(LibuvOutputConsumer consumer, IPipeReader outputReader, Frame frame)

View File

@ -1,42 +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;
using System.Threading;
using System.Threading.Tasks;
using Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http;
using Microsoft.AspNetCore.Server.Kestrel.Internal.System.IO.Pipelines;
using Microsoft.Extensions.Internal;
namespace Microsoft.AspNetCore.Testing
{
public class MockSocketOutput : ISocketOutput
{
public MockSocketOutput()
{
}
public void Write(ArraySegment<byte> buffer, bool chunk = false)
{
}
public Task WriteAsync(ArraySegment<byte> buffer, bool chunk = false, CancellationToken cancellationToken = default(CancellationToken))
{
return TaskCache.CompletedTask;
}
public void Flush()
{
}
public Task FlushAsync(CancellationToken cancellationToken = new CancellationToken())
{
return TaskCache.CompletedTask;
}
public void Write<T>(Action<WritableBuffer, T> write, T state) where T : struct
{
}
}
}