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
# Visual Studio 15
VisualStudioVersion = 15.0.26405.0
VisualStudioVersion = 15.0.26403.7
MinimumVisualStudioVersion = 10.0.40219.1
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Solution Items", "Solution Items", "{7972A5D6-3385-4127-9277-428506DD44FF}"
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\MockFrameControl.cs = test\shared\MockFrameControl.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\StringExtensions.cs = test\shared\StringExtensions.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.Infrastructure;
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
{
@ -15,77 +15,149 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Adapter.Internal
{
private const int MinAllocBufferSize = 2048;
private readonly Stream _filteredStream;
private readonly StreamSocketOutput _output;
private readonly IKestrelTrace _trace;
private readonly IPipeWriter _transportOutputPipeWriter;
private readonly IPipeReader _transportInputPipeReader;
public AdaptedPipeline(
Stream filteredStream,
IPipe inputPipe,
IPipe outputPipe,
IKestrelTrace trace)
public AdaptedPipeline(IPipeReader transportInputPipeReader,
IPipeWriter transportOutputPipeWriter,
IPipe inputPipe,
IPipe outputPipe,
IKestrelTrace trace)
{
_transportInputPipeReader = transportInputPipeReader;
_transportOutputPipeWriter = transportOutputPipeWriter;
Input = inputPipe;
_output = new StreamSocketOutput(filteredStream, outputPipe);
_filteredStream = filteredStream;
Output = outputPipe;
_trace = trace;
}
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();
var outputTask = _output.WriteOutputAsync();
var inputTask = ReadInputAsync(stream);
var outputTask = WriteOutputAsync(stream);
await inputTask;
_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)}");
}
await inputTask;
await outputTask;
}
private async Task ReadInputAsync()
private async Task WriteOutputAsync(Stream stream)
{
int bytesRead;
Exception error = null;
do
try
{
var block = Input.Writer.Alloc(MinAllocBufferSize);
try
if (stream == null)
{
var array = block.Buffer.GetArray();
return;
}
while (true)
{
var readResult = await Output.Reader.ReadAsync();
var buffer = readResult.Buffer;
try
{
bytesRead = await _filteredStream.ReadAsync(array.Array, array.Offset, array.Count);
block.Advance(bytesRead);
if (buffer.IsEmpty && readResult.IsCompleted)
{
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
{
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);
// Don't rethrow the exception. It should be handled by the Pipeline consumer.
return;
// If the stream is null then we're going to abort the connection
throw new ConnectionAbortedException();
}
} 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.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
@ -13,9 +12,9 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Adapter.Internal
public class RawStream : Stream
{
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;
_output = output;
@ -71,19 +70,10 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Adapter.Internal
public override void Write(byte[] buffer, int offset, int count)
{
ArraySegment<byte> segment;
if (buffer != null)
{
segment = new ArraySegment<byte>(buffer, offset, count);
}
else
{
segment = default(ArraySegment<byte>);
}
_output.Write(segment);
WriteAsync(buffer, offset, count).GetAwaiter().GetResult();
}
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;
if (buffer != null)
@ -94,17 +84,19 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Adapter.Internal
{
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()
{
_output.Flush();
FlushAsync(CancellationToken.None).GetAwaiter().GetResult();
}
public override Task FlushAsync(CancellationToken cancellationToken)
{
return _output.FlushAsync(cancellationToken);
return WriteAsync(null, 0, 0, cancellationToken);
}
private async Task<int> ReadAsync(ArraySegment<byte> buffer)
@ -209,11 +201,5 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Adapter.Internal
}, tcs, cancellationToken);
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
};
// TODO: Untangle this mess
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
{
@ -55,7 +52,6 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal
Frame = frame,
Input = inputPipe,
Output = outputPipe,
OutputProducer = outputProducer
});
_serviceContext.Log.ConnectionStart(connectionId);

View File

@ -28,7 +28,6 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal
private TimeoutAction _timeoutAction;
private Task _lifetimeTask;
private Stream _filteredStream;
public FrameConnection(FrameConnectionContext context)
{
@ -69,46 +68,39 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal
private async Task ProcessRequestsAsync()
{
RawStream rawStream = null;
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;
_frame.Output = _context.OutputProducer;
}
else
{
rawStream = new RawStream(_context.Input.Reader, _context.OutputProducer);
try
{
var adaptedPipeline = await ApplyConnectionAdaptersAsync(rawStream);
_frame.Input = adaptedPipeline.Input.Reader;
_frame.Output = adaptedPipeline.Output;
adaptedPipelineTask = adaptedPipeline.RunAsync();
}
catch (Exception ex)
{
Log.LogError(0, ex, $"Uncaught exception from the {nameof(IConnectionAdapter.OnConnectionAsync)} method of an {nameof(IConnectionAdapter)}.");
// Since Frame.ProcessRequestsAsync() isn't called, we have to close the socket here.
_context.OutputProducer.Dispose();
await _socketClosedTcs.Task;
return;
}
adaptedPipeline = new AdaptedPipeline(_context.Input.Reader,
_context.Output.Writer,
PipeFactory.Create(AdaptedInputPipeOptions),
PipeFactory.Create(AdaptedOutputPipeOptions),
Log);
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;
_lastTimestamp = _context.ServiceContext.SystemClock.UtcNow.Ticks;
_frame.Input = input;
_frame.Output = new OutputProducer(output, ConnectionId, Log);
_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 adaptedPipelineTask;
@ -121,14 +113,13 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal
finally
{
_context.ServiceContext.ConnectionManager.RemoveConnection(_context.FrameConnectionId);
rawStream?.Dispose();
DisposeAdaptedConnections();
}
}
public void OnConnectionClosed(Exception ex)
{
// Abort the connection (if it isn't already aborted)
// Abort the connection (if not already aborted)
_frame.Abort(ex);
Log.ConnectionStop(ConnectionId);
@ -139,17 +130,21 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal
public Task StopAsync()
{
_frame.Stop();
return _lifetimeTask;
}
public void Abort(Exception ex)
{
// Abort the connection (if not already aborted)
_frame.Abort(ex);
}
public Task AbortAsync(Exception ex)
{
// Abort the connection (if not already aborted)
_frame.Abort(ex);
return _lifetimeTask;
}
@ -158,25 +153,33 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal
_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];
for (var i = 0; i < _connectionAdapters.Count; i++)
try
{
var adaptedConnection = await _connectionAdapters[i].OnConnectionAsync(adapterContext);
adaptedConnections[i] = adaptedConnection;
adapterContext = new ConnectionAdapterContext(adaptedConnection.ConnectionStream);
for (var i = 0; i < _connectionAdapters.Count; i++)
{
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;
_frame.AdaptedConnections = adaptedConnections;
return new AdaptedPipeline(_filteredStream,
PipeFactory.Create(AdaptedInputPipeOptions),
PipeFactory.Create(AdaptedOutputPipeOptions),
Log);
return adapterContext.ConnectionStream;
}
private void DisposeAdaptedConnections()

View File

@ -16,7 +16,6 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal
public PipeFactory PipeFactory { get; set; }
public List<IConnectionAdapter> ConnectionAdapters { get; set; }
public Frame Frame { get; set; }
public OutputProducer OutputProducer { get; set; }
public IPipe Input { 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 IPipeReader Input { get; set; }
public ISocketOutput Output { get; set; }
public OutputProducer Output { get; set; }
public IAdaptedConnection[] AdaptedConnections { get; set; }
public ConnectionLifetimeControl LifetimeControl { get; set; }
public ITimeoutControl TimeoutControl { get; set; }
protected IKestrelTrace Log => ServiceContext.Log;
@ -411,7 +410,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http
_frameStreams?.Abort(error);
LifetimeControl.End(ProduceEndType.SocketDisconnect);
Output.Abort();
// Potentially calling user code. CancelRequestAbortedToken logs any exceptions.
ServiceContext.ThreadPool.UnsafeRun(state => ((Frame)state).CancelRequestAbortedToken(), this);
@ -827,7 +826,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http
if (_keepAlive)
{
LifetimeControl.End(ProduceEndType.ConnectionKeepAlive);
Log.ConnectionKeepAlive(ConnectionId);
}
if (HttpMethods.IsHead(Method) && _responseBytesWritten > 0)
@ -847,7 +846,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http
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)
{
await TryProduceInvalidRequestResponse();
LifetimeControl.End(ProduceEndType.SocketShutdown);
Output.Dispose();
}
}
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 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
{
public class OutputProducer : ISocketOutput, IDisposable
public class OutputProducer : IDisposable
{
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 readonly IPipeWriter _pipe;
private readonly Frame _frame;
// https://github.com/dotnet/corefxlab/issues/1334
// 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 Action _flushCompleted;
public OutputProducer(IPipeWriter pipe, Frame frame, string connectionId, IKestrelTrace log)
public OutputProducer(IPipeWriter pipe, string connectionId, IKestrelTrace log)
{
_pipe = pipe;
_frame = frame;
_connectionId = connectionId;
_log = log;
_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,
CancellationToken cancellationToken,
bool chunk = false)
@ -108,11 +182,6 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http
}
await _flushTcs.Task;
if (cancellationToken.IsCancellationRequested)
{
_frame.Abort(error: null);
}
cancellationToken.ThrowIfCancellationRequested();
}
@ -120,60 +189,5 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http
{
_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.Threading.Tasks;
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;
namespace Microsoft.AspNetCore.Server.Kestrel.Transport.Libuv.Internal
@ -36,48 +37,50 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Transport.Libuv.Internal
while (true)
{
var result = await _pipe.ReadAsync();
var buffer = result.Buffer;
var consumed = buffer.End;
try
{
if (!buffer.IsEmpty)
var result = await _pipe.ReadAsync();
var buffer = result.Buffer;
var consumed = buffer.End;
try
{
var writeReq = pool.Allocate();
try
if (!buffer.IsEmpty)
{
var writeResult = await writeReq.WriteAsync(_socket, buffer);
var writeReq = pool.Allocate();
LogWriteInfo(writeResult.Status, writeResult.Error);
if (writeResult.Error != null)
try
{
consumed = buffer.Start;
throw writeResult.Error;
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);
}
}
finally
if (buffer.IsEmpty && result.IsCompleted)
{
// Make sure we return the writeReq to the pool
pool.Return(writeReq);
break;
}
}
if (result.IsCancelled)
finally
{
break;
}
if (buffer.IsEmpty && result.IsCompleted)
{
break;
_pipe.Advance(consumed);
}
}
finally
catch (ConnectionAbortedException)
{
_pipe.Advance(consumed);
break;
}
}
}

View File

@ -216,6 +216,10 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Transport.Sockets
{
error = null;
}
catch (ConnectionAbortedException)
{
error = null;
}
catch (IOException ex)
{
error = ex;

View File

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

View File

@ -38,7 +38,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Tests
var called = false;
((ISocketOutput)socketOutput).Write((buffer, state) =>
socketOutput.Write((buffer, state) =>
{
called = true;
},
@ -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, frame, "0", serviceContext.Log);
var socketOutput = new OutputProducer(pipe.Writer, "0", serviceContext.Log);
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.IO;
using System.Net;
using System.Net.Sockets;
using System.Threading;
using System.Threading.Tasks;
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]
public async Task ThrowingSynchronousConnectionAdapterDoesNotCrashServer()
{
@ -121,7 +186,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.FunctionalTests
}
public int BytesRead => _rewritingStream.BytesRead;
}
}
private class AsyncConnectionAdapter : IConnectionAdapter
{

View File

@ -1051,24 +1051,28 @@ namespace Microsoft.AspNetCore.Server.Kestrel.FunctionalTests
{
using (var connection = server.CreateConnection())
{
// Never send the body so CopyToAsync always fails.
// Full request and response
await connection.Send(
"POST / HTTP/1.1",
"Host:",
"Content-Length: 5",
"",
"HelloPOST / HTTP/1.1",
"Host:",
"Content-Length: 5",
"",
"");
"Hello");
await connection.ReceiveForcedEnd(
await connection.Receive(
"HTTP/1.1 200 OK",
$"Date: {testContext.DateHeaderValue}",
"Content-Length: 5",
"",
"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.Sockets;
using System.Reflection;
using System.Threading.Tasks;
using Microsoft.AspNetCore.Builder;
using Microsoft.AspNetCore.Hosting;
using Microsoft.AspNetCore.Hosting.Server;
@ -108,6 +109,11 @@ namespace Microsoft.AspNetCore.Server.Kestrel.FunctionalTests
return new TestConnection(Port, AddressFamily);
}
public Task StopAsync()
{
return _host.StopAsync();
}
public void Dispose()
{
_host.Dispose();

View File

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

View File

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

View File

@ -8,7 +8,6 @@ using System.Threading;
using System.Threading.Tasks;
using BenchmarkDotNet.Attributes;
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.Http;
using Microsoft.AspNetCore.Server.Kestrel.Internal.System.IO.Pipelines;
@ -113,7 +112,6 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Performance
var factory = new PipeFactory();
var input = factory.Create();
var output = factory.Create();
var socketOutput = new StreamSocketOutput(Stream.Null, output);
var serviceContext = new ServiceContext
{
@ -129,19 +127,14 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Performance
ConnectionInformation = new MockConnectionInformation()
};
var outputProducer = new OutputProducer(output.Writer, null, null, null);
var frame = new TestFrame<object>(application: null, context: frameContext)
{
Input = input.Reader,
Output = socketOutput,
LifetimeControl = new ConnectionLifetimeControl(null, output.Reader, outputProducer, serviceContext.Log)
};
frame.Output = new OutputProducer(output.Writer, "", null);
frame.Reset();
// Start writing
var ignore = socketOutput.WriteOutputAsync();
_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.Infrastructure;
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.Networking;
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);
// Act
var writeTask = socketOutput.WriteAsync(buffer, default(CancellationToken));
var writeTask = socketOutput.WriteAsync(buffer);
// Assert
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);
// Act
var writeTask = socketOutput.WriteAsync(buffer, default(CancellationToken));
var writeTask = socketOutput.WriteAsync(buffer);
// Assert
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);
// Act
var writeTask = socketOutput.WriteAsync(buffer, default(CancellationToken));
var writeTask = socketOutput.WriteAsync(buffer);
// Assert
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);
// Act
var writeTask1 = socketOutput.WriteAsync(buffer, default(CancellationToken));
var writeTask1 = socketOutput.WriteAsync(buffer);
// Assert
// The first write should pre-complete since it is <= _maxBytesPreCompleted.
Assert.Equal(TaskStatus.RanToCompletion, writeTask1.Status);
// Act
var writeTask2 = socketOutput.WriteAsync(buffer, default(CancellationToken));
var writeTask2 = socketOutput.WriteAsync(buffer);
await _mockLibuv.OnPostTask;
// Assert
@ -275,7 +274,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Transport.Libuv.Tests
var halfWriteBehindBuffer = new ArraySegment<byte>(data, 0, bufferSize);
// Act
var writeTask1 = socketOutput.WriteAsync(halfWriteBehindBuffer, default(CancellationToken));
var writeTask1 = socketOutput.WriteAsync(halfWriteBehindBuffer);
// Assert
// 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);
// 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);
},
halfWriteBehindBuffer);
// Act
var writeTask2 = socketOutput.WriteAsync(halfWriteBehindBuffer, default(CancellationToken));
var writeTask2 = socketOutput.WriteAsync(halfWriteBehindBuffer);
Assert.False(writeTask2.IsCompleted);
var writeTask3 = socketOutput.WriteAsync(halfWriteBehindBuffer, default(CancellationToken));
var writeTask3 = socketOutput.WriteAsync(halfWriteBehindBuffer);
Assert.False(writeTask3.IsCompleted);
// Drain the write queue
@ -345,7 +344,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Transport.Libuv.Tests
var fullBuffer = new ArraySegment<byte>(data, 0, bufferSize);
// Act
var task1Success = socketOutput.WriteAsync(fullBuffer, abortedSource.Token);
var task1Success = socketOutput.WriteAsync(fullBuffer, cancellationToken: abortedSource.Token);
// task1 should complete successfully as < _maxBytesPreCompleted
// First task is completed and successful
@ -354,8 +353,8 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Transport.Libuv.Tests
Assert.False(task1Success.IsFaulted);
// following tasks should wait.
var task2Success = socketOutput.WriteAsync(fullBuffer, default(CancellationToken));
var task3Canceled = socketOutput.WriteAsync(fullBuffer, abortedSource.Token);
var task2Success = socketOutput.WriteAsync(fullBuffer);
var task3Canceled = socketOutput.WriteAsync(fullBuffer, cancellationToken: abortedSource.Token);
// Give time for tasks to percolate
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,
// 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.False(task4Success.IsCanceled);
Assert.False(task4Success.IsFaulted);
@ -429,7 +428,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Transport.Libuv.Tests
var fullBuffer = new ArraySegment<byte>(data, 0, bufferSize);
// Act
var task1Success = socketOutput.WriteAsync(fullBuffer, abortedSource.Token);
var task1Success = socketOutput.WriteAsync(fullBuffer, cancellationToken: abortedSource.Token);
// task1 should complete successfully as < _maxBytesPreCompleted
// First task is completed and successful
@ -438,7 +437,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Transport.Libuv.Tests
Assert.False(task1Success.IsFaulted);
// 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
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,
// 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.False(task4Success.IsCanceled);
Assert.False(task4Success.IsFaulted);
@ -504,7 +503,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Transport.Libuv.Tests
var fullBuffer = new ArraySegment<byte>(data, 0, bufferSize);
// Act
var task1Waits = socketOutput.WriteAsync(fullBuffer, default(CancellationToken));
var task1Waits = socketOutput.WriteAsync(fullBuffer);
// First task is not completed
Assert.False(task1Waits.IsCompleted);
@ -512,7 +511,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Transport.Libuv.Tests
Assert.False(task1Waits.IsFaulted);
// 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
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,
// 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.False(task4Success.IsCanceled);
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);
// 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
// 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);
// Act
var writeTask2 = socketOutput.WriteAsync(buffer, default(CancellationToken));
var writeTask3 = socketOutput.WriteAsync(buffer, default(CancellationToken));
var writeTask2 = socketOutput.WriteAsync(buffer);
var writeTask3 = socketOutput.WriteAsync(buffer);
// Drain the write queue
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
// are made before write is scheduled
var ignore = socketOutput.WriteAsync(buffer, CancellationToken.None);
ignore = socketOutput.WriteAsync(buffer, CancellationToken.None);
var ignore = socketOutput.WriteAsync(buffer);
ignore = socketOutput.WriteAsync(buffer);
_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 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);
frame.LifetimeControl = new ConnectionLifetimeControl("0", pipe.Reader, socketOutput, serviceContext.Log);
frame.Output = outputProducer;
if (cts != null)
{
@ -683,7 +681,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Transport.Libuv.Tests
var ignore = WriteOutputAsync(consumer, pipe.Reader, frame);
return socketOutput;
return outputProducer;
}
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
{
}
}
}