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) else if (buffer.IsSingleSegment)
{ {
#if NETCOREAPP2_1
await stream.WriteAsync(buffer.First);
#else
var array = buffer.First.GetArray(); var array = buffer.First.GetArray();
await stream.WriteAsync(array.Array, array.Offset, array.Count); await stream.WriteAsync(array.Array, array.Offset, array.Count);
#endif
} }
else else
{ {
foreach (var memory in buffer) foreach (var memory in buffer)
{ {
#if NETCOREAPP2_1
await stream.WriteAsync(memory);
#else
var array = memory.GetArray(); var array = memory.GetArray();
await stream.WriteAsync(array.Array, array.Offset, array.Count); 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 outputBuffer = Input.Writer.GetMemory(MinAllocBufferSize);
var array = outputBuffer.GetArray();
try 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); var bytesRead = await stream.ReadAsync(array.Array, array.Offset, array.Count);
#endif
Input.Writer.Advance(bytesRead); Input.Writer.Advance(bytesRead);
if (bytesRead == 0) 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) public override int Read(byte[] buffer, int offset, int count)
{ {
int read = _inner.Read(buffer, offset, count); int read = _inner.Read(buffer, offset, count);
Log("Read", read, buffer, offset); Log("Read", new ReadOnlySpan<byte>(buffer, offset, read));
return 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) public async override Task<int> ReadAsync(byte[] buffer, int offset, int count, CancellationToken cancellationToken)
{ {
int read = await _inner.ReadAsync(buffer, offset, count, 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; 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) public override long Seek(long offset, SeekOrigin origin)
{ {
return _inner.Seek(offset, 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) 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); _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) 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); 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 // 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(buffer[i].ToString("X2"));
builder.Append(" "); builder.Append(" ");
} }
builder.AppendLine(); builder.AppendLine();
// Write the bytes as if they were ASCII // 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]); 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 // 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 // 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) 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) public override void Write(byte[] buffer, int offset, int count)
{ {
WriteAsync(buffer, offset, count).GetAwaiter().GetResult(); 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) if (buffer != null)
{ {
_output.Write(new ReadOnlySpan<byte>(buffer, offset, count)); _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() public override void Flush()
{ {
FlushAsync(CancellationToken.None).GetAwaiter().GetResult(); FlushAsync(CancellationToken.None).GetAwaiter().GetResult();
@ -94,7 +109,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Adapter.Internal
return WriteAsync(null, 0, 0, cancellationToken); return WriteAsync(null, 0, 0, cancellationToken);
} }
private async Task<int> ReadAsync(ArraySegment<byte> buffer) private async ValueTask<int> ReadAsyncInternal(Memory<byte> destination)
{ {
while (true) while (true)
{ {
@ -105,9 +120,9 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Adapter.Internal
if (!readableBuffer.IsEmpty) if (!readableBuffer.IsEmpty)
{ {
// buffer.Count is int // 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 = readableBuffer.Slice(0, count);
readableBuffer.CopyTo(buffer); readableBuffer.CopyTo(destination.Span);
return count; return count;
} }
else if (result.IsCompleted) else if (result.IsCompleted)

View File

@ -14,10 +14,10 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http
{ {
public class Http1OutputProducer : IHttpOutputProducer 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[] _bytesHttpVersion11 = Encoding.ASCII.GetBytes("HTTP/1.1 ");
private static readonly byte[] _bytesEndHeaders = Encoding.ASCII.GetBytes("\r\n\r\n"); 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 string _connectionId;
private readonly ITimeoutControl _timeoutControl; private readonly ITimeoutControl _timeoutControl;
@ -53,7 +53,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http
_flushCompleted = OnFlushCompleted; _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) if (cancellationToken.IsCancellationRequested)
{ {
@ -65,7 +65,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http
public Task WriteStreamSuffixAsync(CancellationToken cancellationToken) public Task WriteStreamSuffixAsync(CancellationToken cancellationToken)
{ {
return WriteAsync(_endChunkedResponseBytes, cancellationToken); return WriteAsync(_endChunkedResponseBytes.Span, cancellationToken);
} }
public Task FlushAsync(CancellationToken cancellationToken = default(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) public Task Write100ContinueAsync(CancellationToken cancellationToken)
{ {
return WriteAsync(_continueBytes, default(CancellationToken)); return WriteAsync(_continueBytes.Span, default(CancellationToken));
} }
private Task WriteAsync( private Task WriteAsync(
ArraySegment<byte> buffer, ReadOnlySpan<byte> buffer,
CancellationToken cancellationToken) CancellationToken cancellationToken)
{ {
var writableBuffer = default(PipeWriter); var writableBuffer = default(PipeWriter);
@ -178,10 +178,10 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http
writableBuffer = _pipeWriter; writableBuffer = _pipeWriter;
var writer = OutputWriter.Create(writableBuffer); var writer = OutputWriter.Create(writableBuffer);
if (buffer.Count > 0) if (buffer.Length > 0)
{ {
writer.Write(new ReadOnlySpan<byte>(buffer.Array, buffer.Offset, buffer.Count)); writer.Write(buffer);
bytesWritten += buffer.Count; bytesWritten += buffer.Length;
} }
writableBuffer.Commit(); 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[] _bytesConnectionKeepAlive = Encoding.ASCII.GetBytes("\r\nConnection: keep-alive");
private static readonly byte[] _bytesTransferEncodingChunked = Encoding.ASCII.GetBytes("\r\nTransfer-Encoding: chunked"); 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 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 _onStartingSync = new Object();
private readonly object _onCompletedSync = new Object(); private readonly object _onCompletedSync = new Object();
@ -762,14 +762,14 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http
await Output.FlushAsync(cancellationToken); 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. // For the first write, ensure headers are flushed if WriteDataAsync isn't called.
var firstWrite = !HasResponseStarted; var firstWrite = !HasResponseStarted;
if (firstWrite) if (firstWrite)
{ {
var initializeTask = InitializeResponseAsync(data.Count); var initializeTask = InitializeResponseAsync(data.Length);
// If return is Task.CompletedTask no awaiting is required // If return is Task.CompletedTask no awaiting is required
if (!ReferenceEquals(initializeTask, Task.CompletedTask)) if (!ReferenceEquals(initializeTask, Task.CompletedTask))
{ {
@ -778,14 +778,14 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http
} }
else else
{ {
VerifyAndUpdateWrite(data.Count); VerifyAndUpdateWrite(data.Length);
} }
if (_canHaveBody) if (_canHaveBody)
{ {
if (_autoChunk) if (_autoChunk)
{ {
if (data.Count == 0) if (data.Length == 0)
{ {
return !firstWrite ? Task.CompletedTask : FlushAsync(cancellationToken); return !firstWrite ? Task.CompletedTask : FlushAsync(cancellationToken);
} }
@ -794,7 +794,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http
else else
{ {
CheckLastWrite(); CheckLastWrite();
return Output.WriteDataAsync(data, cancellationToken: cancellationToken); return Output.WriteDataAsync(data.Span, cancellationToken: cancellationToken);
} }
} }
else 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; await initializeTask;
@ -814,7 +814,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http
{ {
if (_autoChunk) if (_autoChunk)
{ {
if (data.Count == 0) if (data.Length == 0)
{ {
await FlushAsync(cancellationToken); await FlushAsync(cancellationToken);
return; return;
@ -825,7 +825,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http
else else
{ {
CheckLastWrite(); CheckLastWrite();
await Output.WriteDataAsync(data, cancellationToken: cancellationToken); await Output.WriteDataAsync(data.Span, cancellationToken: cancellationToken);
} }
} }
else 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); 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); var writer = OutputWriter.Create(writableBuffer);
if (buffer.Count > 0) if (buffer.Length > 0)
{ {
ChunkWriter.WriteBeginChunkBytes(ref writer, buffer.Count); ChunkWriter.WriteBeginChunkBytes(ref writer, buffer.Length);
writer.Write(new ReadOnlySpan<byte>(buffer.Array, buffer.Offset, buffer.Count)); writer.Write(buffer.Span);
ChunkWriter.WriteEndChunkBytes(ref writer); ChunkWriter.WriteEndChunkBytes(ref writer);
} }
} }

View File

@ -3,11 +3,12 @@
using System; using System;
using System.IO; using System.IO;
using System.Runtime.ExceptionServices;
using System.Threading; using System.Threading;
using System.Threading.Tasks; using System.Threading.Tasks;
using Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Infrastructure;
using Microsoft.AspNetCore.Http.Features; using Microsoft.AspNetCore.Http.Features;
using Microsoft.AspNetCore.Protocols; using Microsoft.AspNetCore.Protocols;
using Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Infrastructure;
namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http 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) public override Task<int> ReadAsync(byte[] buffer, int offset, int count, CancellationToken cancellationToken)
{ {
var task = ValidateState(cancellationToken); ValidateState(cancellationToken);
if (task != null)
{
return task;
}
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 try
{ {
return await _body.ReadAsync(new ArraySegment<byte>(buffer, offset, count), cancellationToken); return await _body.ReadAsync(buffer, cancellationToken);
} }
catch (ConnectionAbortedException ex) catch (ConnectionAbortedException ex)
{ {
@ -137,11 +143,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http
throw new ArgumentException(CoreStrings.PositiveNumberRequired, nameof(bufferSize)); throw new ArgumentException(CoreStrings.PositiveNumberRequired, nameof(bufferSize));
} }
var task = ValidateState(cancellationToken); ValidateState(cancellationToken);
if (task != null)
{
return task;
}
return CopyToAsyncInternal(destination, 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) switch (_state)
{ {
case HttpStreamState.Open: case HttpStreamState.Open:
if (cancellationToken.IsCancellationRequested) if (cancellationToken.IsCancellationRequested)
{ {
return Task.FromCanceled<int>(cancellationToken); cancellationToken.ThrowIfCancellationRequested();
} }
break; break;
case HttpStreamState.Closed: case HttpStreamState.Closed:
throw new ObjectDisposedException(nameof(HttpRequestStream)); throw new ObjectDisposedException(nameof(HttpRequestStream));
case HttpStreamState.Aborted: case HttpStreamState.Aborted:
return _error != null ? if (_error != null)
Task.FromException<int>(_error) : {
Task.FromCanceled<int>(new CancellationToken(true)); 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) public override Task FlushAsync(CancellationToken cancellationToken)
{ {
var task = ValidateState(cancellationToken); ValidateState(cancellationToken);
if (task == null)
{ return _httpResponseControl.FlushAsync(cancellationToken);
return _httpResponseControl.FlushAsync(cancellationToken);
}
return task;
} }
public override long Seek(long offset, SeekOrigin origin) 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) public override Task WriteAsync(byte[] buffer, int offset, int count, CancellationToken cancellationToken)
{ {
var task = ValidateState(cancellationToken); ValidateState(cancellationToken);
if (task == null)
{ return _httpResponseControl.WriteAsync(new ReadOnlyMemory<byte>(buffer, offset, count), cancellationToken);
return _httpResponseControl.WriteAsync(new ArraySegment<byte>(buffer, offset, count), cancellationToken);
}
return task;
} }
#if NETCOREAPP2_1
public override Task WriteAsync(ReadOnlyMemory<byte> source, CancellationToken cancellationToken = default)
{
ValidateState(cancellationToken);
return _httpResponseControl.WriteAsync(source, cancellationToken);
}
#endif
public void StartAcceptingWrites() public void StartAcceptingWrites()
{ {
// Only start if not aborted // 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) switch (_state)
{ {
case HttpStreamState.Open: case HttpStreamState.Open:
if (cancellationToken.IsCancellationRequested) if (cancellationToken.IsCancellationRequested)
{ {
return Task.FromCanceled(cancellationToken); cancellationToken.ThrowIfCancellationRequested();
} }
break; break;
case HttpStreamState.Closed: case HttpStreamState.Closed:
@ -163,11 +166,10 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http
if (cancellationToken.IsCancellationRequested) if (cancellationToken.IsCancellationRequested)
{ {
// Aborted state only throws on write if cancellationToken requests it // Aborted state only throws on write if cancellationToken requests it
return Task.FromCanceled(cancellationToken); cancellationToken.ThrowIfCancellationRequested();
} }
break; break;
} }
return null;
} }
} }
} }

View File

@ -15,7 +15,10 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http
Task FlushAsync(CancellationToken cancellationToken); Task FlushAsync(CancellationToken cancellationToken);
Task Write100ContinueAsync(CancellationToken cancellationToken); Task Write100ContinueAsync(CancellationToken cancellationToken);
void WriteResponseHeaders(int statusCode, string ReasonPhrase, HttpResponseHeaders responseHeaders); 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); Task WriteStreamSuffixAsync(CancellationToken cancellationToken);
} }
} }

View File

@ -10,7 +10,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http
public interface IHttpResponseControl public interface IHttpResponseControl
{ {
void ProduceContinue(); void ProduceContinue();
Task WriteAsync(ArraySegment<byte> data, CancellationToken cancellationToken); Task WriteAsync(ReadOnlyMemory<byte> data, CancellationToken cancellationToken);
Task FlushAsync(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; 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(); TryInit();
@ -50,10 +50,10 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http
if (!readableBuffer.IsEmpty) if (!readableBuffer.IsEmpty)
{ {
// buffer.Count is int // 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); var slice = readableBuffer.Slice(0, actual);
consumed = readableBuffer.GetPosition(readableBuffer.Start, actual); consumed = readableBuffer.GetPosition(readableBuffer.Start, actual);
slice.CopyTo(buffer); slice.CopyTo(buffer.Span);
return actual; return actual;
} }
else if (result.IsCompleted) else if (result.IsCompleted)
@ -84,8 +84,15 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http
{ {
foreach (var memory in readableBuffer) 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(); var array = memory.GetArray();
await destination.WriteAsync(array.Array, array.Offset, array.Count, cancellationToken); await destination.WriteAsync(array.Array, array.Offset, array.Count, cancellationToken);
#endif
} }
} }
else if (result.IsCompleted) else if (result.IsCompleted)
@ -148,7 +155,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http
public override bool IsEmpty => true; 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; 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]; 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 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); => 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>(); 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) lock (_writeLock)
{ {
@ -182,7 +182,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http2
} }
// Must be called with _writeLock // Must be called with _writeLock
private void Append(ArraySegment<byte> data) private void Append(ReadOnlySpan<byte> data)
{ {
if (_completed) if (_completed)
{ {
@ -194,15 +194,20 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http2
} }
// Must be called with _writeLock // 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) if (_completed)
{ {
return; return Task.CompletedTask;
} }
_outputWriter.Write(data); _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) 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 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); 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 FlushAsync(CancellationToken cancellationToken = default(CancellationToken));
Task Write100ContinueAsync(int streamId); Task Write100ContinueAsync(int streamId);
void WriteResponseHeaders(int streamId, int statusCode, IHeaderDictionary headers); void WriteResponseHeaders(int streamId, int statusCode, IHeaderDictionary headers);
Task WriteDataAsync(int streamId, Span<byte> data, CancellationToken cancellationToken); Task WriteDataAsync(int streamId, ReadOnlySpan<byte> data, CancellationToken cancellationToken);
Task WriteDataAsync(int streamId, Span<byte> data, bool endStream, CancellationToken cancellationToken); Task WriteDataAsync(int streamId, ReadOnlySpan<byte> data, bool endStream, CancellationToken cancellationToken);
Task WriteRstStreamAsync(int streamId, Http2ErrorCode errorCode); Task WriteRstStreamAsync(int streamId, Http2ErrorCode errorCode);
Task WriteSettingsAckAsync(); Task WriteSettingsAckAsync();
Task WritePingAsync(Http2PingFrameFlags flags, Span<byte> payload); Task WritePingAsync(Http2PingFrameFlags flags, ReadOnlySpan<byte> payload);
Task WriteGoAwayAsync(int lastStreamId, Http2ErrorCode errorCode); 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); 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; var writableBuffer = _pair.Application.Output;
writableBuffer.Write(span); writableBuffer.Write(span);
return FlushAsync(writableBuffer);
}
private static async Task FlushAsync(PipeWriter writableBuffer)
{
await writableBuffer.FlushAsync(); await writableBuffer.FlushAsync();
} }

View File

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

View File

@ -110,7 +110,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Tests
var mockBodyControl = new Mock<IHttpBodyControlFeature>(); var mockBodyControl = new Mock<IHttpBodyControlFeature>();
mockBodyControl.Setup(m => m.AllowSynchronousIO).Returns(() => allowSynchronousIO); mockBodyControl.Setup(m => m.AllowSynchronousIO).Returns(() => allowSynchronousIO);
var mockHttpResponseControl = new Mock<IHttpResponseControl>(); 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); var stream = new HttpResponseStream(mockBodyControl.Object, mockHttpResponseControl.Object);
stream.StartAcceptingWrites(); stream.StartAcceptingWrites();

View File

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