Updated Streams to override Memory and Span overloads (#2333)

- Also plumbed Memory/Span through Kestrel over ArraySegment.
- Throw synchronously from the HttpRequestStream instead of async in some cases.
This commit is contained in:
David Fowler 2018-02-21 00:00:46 -08:00 committed by GitHub
parent a72e5db797
commit 3fc69dc71f
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
19 changed files with 199 additions and 113 deletions

View File

@ -79,15 +79,23 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Adapter.Internal
}
else if (buffer.IsSingleSegment)
{
#if NETCOREAPP2_1
await stream.WriteAsync(buffer.First);
#else
var array = buffer.First.GetArray();
await stream.WriteAsync(array.Array, array.Offset, array.Count);
#endif
}
else
{
foreach (var memory in buffer)
{
#if NETCOREAPP2_1
await stream.WriteAsync(memory);
#else
var array = memory.GetArray();
await stream.WriteAsync(array.Array, array.Offset, array.Count);
#endif
}
}
}
@ -125,10 +133,14 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Adapter.Internal
var outputBuffer = Input.Writer.GetMemory(MinAllocBufferSize);
var array = outputBuffer.GetArray();
try
{
#if NETCOREAPP2_1
var bytesRead = await stream.ReadAsync(outputBuffer);
#else
var array = outputBuffer.GetArray();
var bytesRead = await stream.ReadAsync(array.Array, array.Offset, array.Count);
#endif
Input.Writer.Advance(bytesRead);
if (bytesRead == 0)

View File

@ -79,17 +79,35 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Adapter.Internal
public override int Read(byte[] buffer, int offset, int count)
{
int read = _inner.Read(buffer, offset, count);
Log("Read", read, buffer, offset);
Log("Read", new ReadOnlySpan<byte>(buffer, offset, read));
return read;
}
#if NETCOREAPP2_1
public override int Read(Span<byte> destination)
{
int read = _inner.Read(destination);
Log("Read", destination.Slice(0, read));
return read;
}
#endif
public async override Task<int> ReadAsync(byte[] buffer, int offset, int count, CancellationToken cancellationToken)
{
int read = await _inner.ReadAsync(buffer, offset, count, cancellationToken);
Log("ReadAsync", read, buffer, offset);
Log("ReadAsync", new ReadOnlySpan<byte>(buffer, offset, read));
return read;
}
#if NETCOREAPP2_1
public override async ValueTask<int> ReadAsync(Memory<byte> destination, CancellationToken cancellationToken = default)
{
int read = await _inner.ReadAsync(destination, cancellationToken);
Log("ReadAsync", destination.Span.Slice(0, read));
return read;
}
#endif
public override long Seek(long offset, SeekOrigin origin)
{
return _inner.Seek(offset, origin);
@ -102,29 +120,45 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Adapter.Internal
public override void Write(byte[] buffer, int offset, int count)
{
Log("Write", count, buffer, offset);
Log("Write", new ReadOnlySpan<byte>(buffer, offset, count));
_inner.Write(buffer, offset, count);
}
#if NETCOREAPP2_1
public override void Write(ReadOnlySpan<byte> source)
{
Log("Write", source);
_inner.Write(source);
}
#endif
public override Task WriteAsync(byte[] buffer, int offset, int count, CancellationToken cancellationToken)
{
Log("WriteAsync", count, buffer, offset);
Log("WriteAsync", new ReadOnlySpan<byte>(buffer, offset, count));
return _inner.WriteAsync(buffer, offset, count, cancellationToken);
}
private void Log(string method, int count, byte[] buffer, int offset)
#if NETCOREAPP2_1
public override Task WriteAsync(ReadOnlyMemory<byte> source, CancellationToken cancellationToken = default)
{
var builder = new StringBuilder($"{method}[{count}] ");
Log("WriteAsync", source.Span);
return _inner.WriteAsync(source, cancellationToken);
}
#endif
private void Log(string method, ReadOnlySpan<byte> buffer)
{
var builder = new StringBuilder($"{method}[{buffer.Length}] ");
// Write the hex
for (int i = offset; i < offset + count; i++)
for (int i = 0; i < buffer.Length; i++)
{
builder.Append(buffer[i].ToString("X2"));
builder.Append(" ");
}
builder.AppendLine();
// Write the bytes as if they were ASCII
for (int i = offset; i < offset + count; i++)
for (int i = 0; i < buffer.Length; i++)
{
builder.Append((char)buffer[i]);
}

View File

@ -61,29 +61,44 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Adapter.Internal
{
// ValueTask uses .GetAwaiter().GetResult() if necessary
// https://github.com/dotnet/corefx/blob/f9da3b4af08214764a51b2331f3595ffaf162abe/src/System.Threading.Tasks.Extensions/src/System/Threading/Tasks/ValueTask.cs#L156
return ReadAsync(new ArraySegment<byte>(buffer, offset, count)).Result;
return ReadAsyncInternal(new Memory<byte>(buffer, offset, count)).Result;
}
public override Task<int> ReadAsync(byte[] buffer, int offset, int count, CancellationToken cancellationToken)
{
return ReadAsync(new ArraySegment<byte>(buffer, offset, count));
return ReadAsyncInternal(new Memory<byte>(buffer, offset, count)).AsTask();
}
#if NETCOREAPP2_1
public override ValueTask<int> ReadAsync(Memory<byte> destination, CancellationToken cancellationToken = default)
{
return ReadAsyncInternal(destination);
}
#endif
public override void Write(byte[] buffer, int offset, int count)
{
WriteAsync(buffer, offset, count).GetAwaiter().GetResult();
}
public override async Task WriteAsync(byte[] buffer, int offset, int count, CancellationToken token)
public override async Task WriteAsync(byte[] buffer, int offset, int count, CancellationToken cancellationToken)
{
if (buffer != null)
{
_output.Write(new ReadOnlySpan<byte>(buffer, offset, count));
}
await _output.FlushAsync(token);
await _output.FlushAsync(cancellationToken);
}
#if NETCOREAPP2_1
public override async Task WriteAsync(ReadOnlyMemory<byte> source, CancellationToken cancellationToken = default)
{
_output.Write(source.Span);
await _output.FlushAsync(cancellationToken);
}
#endif
public override void Flush()
{
FlushAsync(CancellationToken.None).GetAwaiter().GetResult();
@ -94,7 +109,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Adapter.Internal
return WriteAsync(null, 0, 0, cancellationToken);
}
private async Task<int> ReadAsync(ArraySegment<byte> buffer)
private async ValueTask<int> ReadAsyncInternal(Memory<byte> destination)
{
while (true)
{
@ -105,9 +120,9 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Adapter.Internal
if (!readableBuffer.IsEmpty)
{
// buffer.Count is int
var count = (int) Math.Min(readableBuffer.Length, buffer.Count);
var count = (int) Math.Min(readableBuffer.Length, destination.Length);
readableBuffer = readableBuffer.Slice(0, count);
readableBuffer.CopyTo(buffer);
readableBuffer.CopyTo(destination.Span);
return count;
}
else if (result.IsCompleted)

View File

@ -14,10 +14,10 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http
{
public class Http1OutputProducer : IHttpOutputProducer
{
private static readonly ArraySegment<byte> _continueBytes = new ArraySegment<byte>(Encoding.ASCII.GetBytes("HTTP/1.1 100 Continue\r\n\r\n"));
private static readonly ReadOnlyMemory<byte> _continueBytes = new ReadOnlyMemory<byte>(Encoding.ASCII.GetBytes("HTTP/1.1 100 Continue\r\n\r\n"));
private static readonly byte[] _bytesHttpVersion11 = Encoding.ASCII.GetBytes("HTTP/1.1 ");
private static readonly byte[] _bytesEndHeaders = Encoding.ASCII.GetBytes("\r\n\r\n");
private static readonly ArraySegment<byte> _endChunkedResponseBytes = new ArraySegment<byte>(Encoding.ASCII.GetBytes("0\r\n\r\n"));
private static readonly ReadOnlyMemory<byte> _endChunkedResponseBytes = new ReadOnlyMemory<byte>(Encoding.ASCII.GetBytes("0\r\n\r\n"));
private readonly string _connectionId;
private readonly ITimeoutControl _timeoutControl;
@ -53,7 +53,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http
_flushCompleted = OnFlushCompleted;
}
public Task WriteDataAsync(ArraySegment<byte> buffer, CancellationToken cancellationToken = default(CancellationToken))
public Task WriteDataAsync(ReadOnlySpan<byte> buffer, CancellationToken cancellationToken = default(CancellationToken))
{
if (cancellationToken.IsCancellationRequested)
{
@ -65,7 +65,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http
public Task WriteStreamSuffixAsync(CancellationToken cancellationToken)
{
return WriteAsync(_endChunkedResponseBytes, cancellationToken);
return WriteAsync(_endChunkedResponseBytes.Span, cancellationToken);
}
public Task FlushAsync(CancellationToken cancellationToken = default(CancellationToken))
@ -160,11 +160,11 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http
public Task Write100ContinueAsync(CancellationToken cancellationToken)
{
return WriteAsync(_continueBytes, default(CancellationToken));
return WriteAsync(_continueBytes.Span, default(CancellationToken));
}
private Task WriteAsync(
ArraySegment<byte> buffer,
ReadOnlySpan<byte> buffer,
CancellationToken cancellationToken)
{
var writableBuffer = default(PipeWriter);
@ -178,10 +178,10 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http
writableBuffer = _pipeWriter;
var writer = OutputWriter.Create(writableBuffer);
if (buffer.Count > 0)
if (buffer.Length > 0)
{
writer.Write(new ReadOnlySpan<byte>(buffer.Array, buffer.Offset, buffer.Count));
bytesWritten += buffer.Count;
writer.Write(buffer);
bytesWritten += buffer.Length;
}
writableBuffer.Commit();

View File

@ -31,7 +31,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http
private static readonly byte[] _bytesConnectionKeepAlive = Encoding.ASCII.GetBytes("\r\nConnection: keep-alive");
private static readonly byte[] _bytesTransferEncodingChunked = Encoding.ASCII.GetBytes("\r\nTransfer-Encoding: chunked");
private static readonly byte[] _bytesServer = Encoding.ASCII.GetBytes("\r\nServer: " + Constants.ServerName);
private static readonly Action<PipeWriter, ArraySegment<byte>> _writeChunk = WriteChunk;
private static readonly Action<PipeWriter, ReadOnlyMemory<byte>> _writeChunk = WriteChunk;
private readonly object _onStartingSync = new Object();
private readonly object _onCompletedSync = new Object();
@ -762,14 +762,14 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http
await Output.FlushAsync(cancellationToken);
}
public Task WriteAsync(ArraySegment<byte> data, CancellationToken cancellationToken = default(CancellationToken))
public Task WriteAsync(ReadOnlyMemory<byte> data, CancellationToken cancellationToken = default(CancellationToken))
{
// For the first write, ensure headers are flushed if WriteDataAsync isn't called.
var firstWrite = !HasResponseStarted;
if (firstWrite)
{
var initializeTask = InitializeResponseAsync(data.Count);
var initializeTask = InitializeResponseAsync(data.Length);
// If return is Task.CompletedTask no awaiting is required
if (!ReferenceEquals(initializeTask, Task.CompletedTask))
{
@ -778,14 +778,14 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http
}
else
{
VerifyAndUpdateWrite(data.Count);
VerifyAndUpdateWrite(data.Length);
}
if (_canHaveBody)
{
if (_autoChunk)
{
if (data.Count == 0)
if (data.Length == 0)
{
return !firstWrite ? Task.CompletedTask : FlushAsync(cancellationToken);
}
@ -794,7 +794,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http
else
{
CheckLastWrite();
return Output.WriteDataAsync(data, cancellationToken: cancellationToken);
return Output.WriteDataAsync(data.Span, cancellationToken: cancellationToken);
}
}
else
@ -804,7 +804,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http
}
}
public async Task WriteAsyncAwaited(Task initializeTask, ArraySegment<byte> data, CancellationToken cancellationToken)
public async Task WriteAsyncAwaited(Task initializeTask, ReadOnlyMemory<byte> data, CancellationToken cancellationToken)
{
await initializeTask;
@ -814,7 +814,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http
{
if (_autoChunk)
{
if (data.Count == 0)
if (data.Length == 0)
{
await FlushAsync(cancellationToken);
return;
@ -825,7 +825,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http
else
{
CheckLastWrite();
await Output.WriteDataAsync(data, cancellationToken: cancellationToken);
await Output.WriteDataAsync(data.Span, cancellationToken: cancellationToken);
}
}
else
@ -892,18 +892,18 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http
}
}
private Task WriteChunkedAsync(ArraySegment<byte> data, CancellationToken cancellationToken)
private Task WriteChunkedAsync(ReadOnlyMemory<byte> data, CancellationToken cancellationToken)
{
return Output.WriteAsync(_writeChunk, data);
}
private static void WriteChunk(PipeWriter writableBuffer, ArraySegment<byte> buffer)
private static void WriteChunk(PipeWriter writableBuffer, ReadOnlyMemory<byte> buffer)
{
var writer = OutputWriter.Create(writableBuffer);
if (buffer.Count > 0)
if (buffer.Length > 0)
{
ChunkWriter.WriteBeginChunkBytes(ref writer, buffer.Count);
writer.Write(new ReadOnlySpan<byte>(buffer.Array, buffer.Offset, buffer.Count));
ChunkWriter.WriteBeginChunkBytes(ref writer, buffer.Length);
writer.Write(buffer.Span);
ChunkWriter.WriteEndChunkBytes(ref writer);
}
}

View File

@ -3,11 +3,12 @@
using System;
using System.IO;
using System.Runtime.ExceptionServices;
using System.Threading;
using System.Threading.Tasks;
using Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Infrastructure;
using Microsoft.AspNetCore.Http.Features;
using Microsoft.AspNetCore.Protocols;
using Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Infrastructure;
namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http
{
@ -105,20 +106,25 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http
public override Task<int> ReadAsync(byte[] buffer, int offset, int count, CancellationToken cancellationToken)
{
var task = ValidateState(cancellationToken);
if (task != null)
{
return task;
}
ValidateState(cancellationToken);
return ReadAsyncInternal(buffer, offset, count, cancellationToken);
return ReadAsyncInternal(new Memory<byte>(buffer, offset, count), cancellationToken).AsTask();
}
private async Task<int> ReadAsyncInternal(byte[] buffer, int offset, int count, CancellationToken cancellationToken)
#if NETCOREAPP2_1
public override ValueTask<int> ReadAsync(Memory<byte> destination, CancellationToken cancellationToken = default)
{
ValidateState(cancellationToken);
return ReadAsyncInternal(destination, cancellationToken);
}
#endif
private async ValueTask<int> ReadAsyncInternal(Memory<byte> buffer, CancellationToken cancellationToken)
{
try
{
return await _body.ReadAsync(new ArraySegment<byte>(buffer, offset, count), cancellationToken);
return await _body.ReadAsync(buffer, cancellationToken);
}
catch (ConnectionAbortedException ex)
{
@ -137,11 +143,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http
throw new ArgumentException(CoreStrings.PositiveNumberRequired, nameof(bufferSize));
}
var task = ValidateState(cancellationToken);
if (task != null)
{
return task;
}
ValidateState(cancellationToken);
return CopyToAsyncInternal(destination, cancellationToken);
}
@ -193,24 +195,29 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http
}
}
private Task<int> ValidateState(CancellationToken cancellationToken)
private void ValidateState(CancellationToken cancellationToken)
{
switch (_state)
{
case HttpStreamState.Open:
if (cancellationToken.IsCancellationRequested)
{
return Task.FromCanceled<int>(cancellationToken);
cancellationToken.ThrowIfCancellationRequested();
}
break;
case HttpStreamState.Closed:
throw new ObjectDisposedException(nameof(HttpRequestStream));
case HttpStreamState.Aborted:
return _error != null ?
Task.FromException<int>(_error) :
Task.FromCanceled<int>(new CancellationToken(true));
if (_error != null)
{
ExceptionDispatchInfo.Capture(_error).Throw();
}
else
{
throw new TaskCanceledException();
}
break;
}
return null;
}
}
}

View File

@ -41,12 +41,9 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http
public override Task FlushAsync(CancellationToken cancellationToken)
{
var task = ValidateState(cancellationToken);
if (task == null)
{
return _httpResponseControl.FlushAsync(cancellationToken);
}
return task;
ValidateState(cancellationToken);
return _httpResponseControl.FlushAsync(cancellationToken);
}
public override long Seek(long offset, SeekOrigin origin)
@ -109,14 +106,20 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http
public override Task WriteAsync(byte[] buffer, int offset, int count, CancellationToken cancellationToken)
{
var task = ValidateState(cancellationToken);
if (task == null)
{
return _httpResponseControl.WriteAsync(new ArraySegment<byte>(buffer, offset, count), cancellationToken);
}
return task;
ValidateState(cancellationToken);
return _httpResponseControl.WriteAsync(new ReadOnlyMemory<byte>(buffer, offset, count), cancellationToken);
}
#if NETCOREAPP2_1
public override Task WriteAsync(ReadOnlyMemory<byte> source, CancellationToken cancellationToken = default)
{
ValidateState(cancellationToken);
return _httpResponseControl.WriteAsync(source, cancellationToken);
}
#endif
public void StartAcceptingWrites()
{
// Only start if not aborted
@ -147,14 +150,14 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http
}
}
private Task ValidateState(CancellationToken cancellationToken)
private void ValidateState(CancellationToken cancellationToken)
{
switch (_state)
{
case HttpStreamState.Open:
if (cancellationToken.IsCancellationRequested)
{
return Task.FromCanceled(cancellationToken);
cancellationToken.ThrowIfCancellationRequested();
}
break;
case HttpStreamState.Closed:
@ -163,11 +166,10 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http
if (cancellationToken.IsCancellationRequested)
{
// Aborted state only throws on write if cancellationToken requests it
return Task.FromCanceled(cancellationToken);
cancellationToken.ThrowIfCancellationRequested();
}
break;
}
return null;
}
}
}

View File

@ -15,7 +15,10 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http
Task FlushAsync(CancellationToken cancellationToken);
Task Write100ContinueAsync(CancellationToken cancellationToken);
void WriteResponseHeaders(int statusCode, string ReasonPhrase, HttpResponseHeaders responseHeaders);
Task WriteDataAsync(ArraySegment<byte> data, CancellationToken cancellationToken);
// The reason this is ReadOnlySpan and not ReadOnlyMemory is because writes are always
// synchronous. Flushing to get back pressure is the only time we truly go async but
// that's after the buffer is copied
Task WriteDataAsync(ReadOnlySpan<byte> data, CancellationToken cancellationToken);
Task WriteStreamSuffixAsync(CancellationToken cancellationToken);
}
}

View File

@ -10,7 +10,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http
public interface IHttpResponseControl
{
void ProduceContinue();
Task WriteAsync(ArraySegment<byte> data, CancellationToken cancellationToken);
Task WriteAsync(ReadOnlyMemory<byte> data, CancellationToken cancellationToken);
Task FlushAsync(CancellationToken cancellationToken);
}
}

View File

@ -35,7 +35,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http
protected IKestrelTrace Log => _context.ServiceContext.Log;
public virtual async Task<int> ReadAsync(ArraySegment<byte> buffer, CancellationToken cancellationToken = default(CancellationToken))
public virtual async ValueTask<int> ReadAsync(Memory<byte> buffer, CancellationToken cancellationToken = default(CancellationToken))
{
TryInit();
@ -50,10 +50,10 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http
if (!readableBuffer.IsEmpty)
{
// buffer.Count is int
var actual = (int) Math.Min(readableBuffer.Length, buffer.Count);
var actual = (int) Math.Min(readableBuffer.Length, buffer.Length);
var slice = readableBuffer.Slice(0, actual);
consumed = readableBuffer.GetPosition(readableBuffer.Start, actual);
slice.CopyTo(buffer);
slice.CopyTo(buffer.Span);
return actual;
}
else if (result.IsCompleted)
@ -84,8 +84,15 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http
{
foreach (var memory in readableBuffer)
{
// REVIEW: This *could* be slower if 2 things are true
// - The WriteAsync(ReadOnlyMemory<byte>) isn't overridden on the destination
// - We change the Kestrel Memory Pool to not use pinned arrays but instead use native memory
#if NETCOREAPP2_1
await destination.WriteAsync(memory);
#else
var array = memory.GetArray();
await destination.WriteAsync(array.Array, array.Offset, array.Count, cancellationToken);
#endif
}
}
else if (result.IsCompleted)
@ -148,7 +155,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http
public override bool IsEmpty => true;
public override Task<int> ReadAsync(ArraySegment<byte> buffer, CancellationToken cancellationToken = default(CancellationToken)) => Task.FromResult(0);
public override ValueTask<int> ReadAsync(Memory<byte> buffer, CancellationToken cancellationToken = default(CancellationToken)) => new ValueTask<int>(0);
public override Task CopyToAsync(Stream destination, CancellationToken cancellationToken = default(CancellationToken)) => Task.CompletedTask;

View File

@ -19,7 +19,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http2
private readonly byte[] _data = new byte[HeaderLength + MinAllowedMaxFrameSize];
public ArraySegment<byte> Raw => new ArraySegment<byte>(_data, 0, HeaderLength + Length);
public Span<byte> Raw => new Span<byte>(_data, 0, HeaderLength + Length);
public int Length
{

View File

@ -100,10 +100,10 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http2
}
}
public Task WriteDataAsync(int streamId, Span<byte> data, CancellationToken cancellationToken)
public Task WriteDataAsync(int streamId, ReadOnlySpan<byte> data, CancellationToken cancellationToken)
=> WriteDataAsync(streamId, data, endStream: false, cancellationToken: cancellationToken);
public Task WriteDataAsync(int streamId, Span<byte> data, bool endStream, CancellationToken cancellationToken)
public Task WriteDataAsync(int streamId, ReadOnlySpan<byte> data, bool endStream, CancellationToken cancellationToken)
{
var tasks = new List<Task>();
@ -162,7 +162,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http2
}
}
public Task WritePingAsync(Http2PingFrameFlags flags, Span<byte> payload)
public Task WritePingAsync(Http2PingFrameFlags flags, ReadOnlySpan<byte> payload)
{
lock (_writeLock)
{
@ -182,7 +182,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http2
}
// Must be called with _writeLock
private void Append(ArraySegment<byte> data)
private void Append(ReadOnlySpan<byte> data)
{
if (_completed)
{
@ -194,15 +194,20 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http2
}
// Must be called with _writeLock
private async Task WriteAsync(ArraySegment<byte> data, CancellationToken cancellationToken = default(CancellationToken))
private Task WriteAsync(ReadOnlySpan<byte> data, CancellationToken cancellationToken = default(CancellationToken))
{
if (_completed)
{
return;
return Task.CompletedTask;
}
_outputWriter.Write(data);
await _outputWriter.FlushAsync(cancellationToken);
return FlushAsync(_outputWriter, cancellationToken);
}
private async Task FlushAsync(PipeWriter outputWriter, CancellationToken cancellationToken)
{
await outputWriter.FlushAsync(cancellationToken);
}
private static IEnumerable<KeyValuePair<string, string>> EnumerateHeaders(IHeaderDictionary headers)

View File

@ -39,7 +39,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http2
public Task Write100ContinueAsync(CancellationToken cancellationToken) => _frameWriter.Write100ContinueAsync(_streamId);
public Task WriteDataAsync(ArraySegment<byte> data, CancellationToken cancellationToken)
public Task WriteDataAsync(ReadOnlySpan<byte> data, CancellationToken cancellationToken)
{
return _frameWriter.WriteDataAsync(_streamId, data, cancellationToken);
}

View File

@ -14,11 +14,11 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http2
Task FlushAsync(CancellationToken cancellationToken = default(CancellationToken));
Task Write100ContinueAsync(int streamId);
void WriteResponseHeaders(int streamId, int statusCode, IHeaderDictionary headers);
Task WriteDataAsync(int streamId, Span<byte> data, CancellationToken cancellationToken);
Task WriteDataAsync(int streamId, Span<byte> data, bool endStream, CancellationToken cancellationToken);
Task WriteDataAsync(int streamId, ReadOnlySpan<byte> data, CancellationToken cancellationToken);
Task WriteDataAsync(int streamId, ReadOnlySpan<byte> data, bool endStream, CancellationToken cancellationToken);
Task WriteRstStreamAsync(int streamId, Http2ErrorCode errorCode);
Task WriteSettingsAckAsync();
Task WritePingAsync(Http2PingFrameFlags flags, Span<byte> payload);
Task WritePingAsync(Http2PingFrameFlags flags, ReadOnlySpan<byte> payload);
Task WriteGoAwayAsync(int lastStreamId, Http2ErrorCode errorCode);
}
}

View File

@ -2136,10 +2136,15 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Tests
return Task.WhenAll(_runningStreams.Values.Select(tcs => tcs.Task)).TimeoutAfter(TestConstants.DefaultTimeout);
}
private async Task SendAsync(ArraySegment<byte> span)
private Task SendAsync(ReadOnlySpan<byte> span)
{
var writableBuffer = _pair.Application.Output;
writableBuffer.Write(span);
return FlushAsync(writableBuffer);
}
private static async Task FlushAsync(PipeWriter writableBuffer)
{
await writableBuffer.FlushAsync();
}

View File

@ -118,7 +118,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Tests
var mockBodyControl = new Mock<IHttpBodyControlFeature>();
mockBodyControl.Setup(m => m.AllowSynchronousIO).Returns(() => allowSynchronousIO);
var mockMessageBody = new Mock<MessageBody>((HttpProtocol)null);
mockMessageBody.Setup(m => m.ReadAsync(It.IsAny<ArraySegment<byte>>(), CancellationToken.None)).ReturnsAsync(0);
mockMessageBody.Setup(m => m.ReadAsync(It.IsAny<Memory<byte>>(), CancellationToken.None)).Returns(new ValueTask<int>(0));
var stream = new HttpRequestStream(mockBodyControl.Object);
stream.StartAcceptingReads(mockMessageBody.Object);
@ -136,25 +136,23 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Tests
}
[Fact]
public void AbortCausesReadToCancel()
public async Task AbortCausesReadToCancel()
{
var stream = new HttpRequestStream(Mock.Of<IHttpBodyControlFeature>());
stream.StartAcceptingReads(null);
stream.Abort();
var task = stream.ReadAsync(new byte[1], 0, 1);
Assert.True(task.IsCanceled);
await Assert.ThrowsAsync<TaskCanceledException>(() => stream.ReadAsync(new byte[1], 0, 1));
}
[Fact]
public void AbortWithErrorCausesReadToCancel()
public async Task AbortWithErrorCausesReadToCancel()
{
var stream = new HttpRequestStream(Mock.Of<IHttpBodyControlFeature>());
stream.StartAcceptingReads(null);
var error = new Exception();
stream.Abort(error);
var task = stream.ReadAsync(new byte[1], 0, 1);
Assert.True(task.IsFaulted);
Assert.Same(error, task.Exception.InnerException);
var exception = await Assert.ThrowsAsync<Exception>(() => stream.ReadAsync(new byte[1], 0, 1));
Assert.Same(error, exception);
}
[Fact]
@ -167,25 +165,23 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Tests
}
[Fact]
public void AbortCausesCopyToAsyncToCancel()
public async Task AbortCausesCopyToAsyncToCancel()
{
var stream = new HttpRequestStream(Mock.Of<IHttpBodyControlFeature>());
stream.StartAcceptingReads(null);
stream.Abort();
var task = stream.CopyToAsync(Mock.Of<Stream>());
Assert.True(task.IsCanceled);
await Assert.ThrowsAsync<TaskCanceledException>(() => stream.CopyToAsync(Mock.Of<Stream>()));
}
[Fact]
public void AbortWithErrorCausesCopyToAsyncToCancel()
public async Task AbortWithErrorCausesCopyToAsyncToCancel()
{
var stream = new HttpRequestStream(Mock.Of<IHttpBodyControlFeature>());
stream.StartAcceptingReads(null);
var error = new Exception();
stream.Abort(error);
var task = stream.CopyToAsync(Mock.Of<Stream>());
Assert.True(task.IsFaulted);
Assert.Same(error, task.Exception.InnerException);
var exception = await Assert.ThrowsAsync<Exception>(() => stream.CopyToAsync(Mock.Of<Stream>()));
Assert.Same(error, exception);
}
[Fact]

View File

@ -110,7 +110,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Tests
var mockBodyControl = new Mock<IHttpBodyControlFeature>();
mockBodyControl.Setup(m => m.AllowSynchronousIO).Returns(() => allowSynchronousIO);
var mockHttpResponseControl = new Mock<IHttpResponseControl>();
mockHttpResponseControl.Setup(m => m.WriteAsync(It.IsAny<ArraySegment<byte>>(), CancellationToken.None)).Returns(Task.CompletedTask);
mockHttpResponseControl.Setup(m => m.WriteAsync(It.IsAny<ReadOnlyMemory<byte>>(), CancellationToken.None)).Returns(Task.CompletedTask);
var stream = new HttpResponseStream(mockBodyControl.Object, mockHttpResponseControl.Object);
stream.StartAcceptingWrites();

View File

@ -426,7 +426,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Tests
{
var writeCount = 0;
var writeTcs = new TaskCompletionSource<byte[]>();
var mockDestination = new Mock<Stream>();
var mockDestination = new Mock<Stream>() { CallBase = true };
mockDestination
.Setup(m => m.WriteAsync(It.IsAny<byte[]>(), It.IsAny<int>(), It.IsAny<int>(), CancellationToken.None))
@ -595,7 +595,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Tests
// Time out on the next read
input.Http1Connection.SendTimeoutResponse();
var exception = await Assert.ThrowsAsync<BadHttpRequestException>(() => body.ReadAsync(new ArraySegment<byte>(new byte[1])));
var exception = await Assert.ThrowsAsync<BadHttpRequestException>(async () => await body.ReadAsync(new Memory<byte>(new byte[1])));
Assert.Equal(StatusCodes.Status408RequestTimeout, exception.StatusCode);
await body.StopAsync();

View File

@ -19,7 +19,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Tests.TestHelpers
{
}
public Task WriteAsync(ArraySegment<byte> data, CancellationToken cancellationToken)
public Task WriteAsync(ReadOnlyMemory<byte> data, CancellationToken cancellationToken)
{
return Task.CompletedTask;
}