From 2c108d9ba1986748ac3bc512c69a43319de85ddc Mon Sep 17 00:00:00 2001 From: Pavel Krymets Date: Wed, 28 Feb 2018 12:41:51 -0800 Subject: [PATCH] React to pipes in corefx (#2337) --- Directory.Build.props | 2 + ...Http1ConnectionParsingOverheadBenchmark.cs | 5 +- .../Http1WritingBenchmark.cs | 60 +-- .../HttpParserBenchmark.cs | 4 +- .../HttpProtocolFeatureCollection.cs | 3 +- .../Kestrel.Performance/Mocks/NullParser.cs | 4 +- .../PipeThroughputBenchmark.cs | 12 +- .../RequestParsingBenchmark.cs | 18 +- .../ResponseHeaderCollectionBenchmark.cs | 3 +- .../ResponseHeadersWritingBenchmark.cs | 15 +- build/dependencies.props | 59 ++- .../Adapter/Internal/AdaptedPipeline.cs | 25 +- .../Internal/ConnectionHandler.cs | 4 +- src/Kestrel.Core/Internal/Http/ChunkWriter.cs | 6 +- .../Internal/Http/Http1Connection.cs | 15 +- .../Internal/Http/Http1ConnectionContext.cs | 2 +- .../Internal/Http/Http1MessageBody.cs | 33 +- .../Internal/Http/Http1OutputProducer.cs | 13 +- .../Internal/Http/HttpHeaders.Generated.cs | 2 +- src/Kestrel.Core/Internal/Http/HttpParser.cs | 11 +- .../Internal/Http/HttpProtocol.cs | 5 +- .../Internal/Http/HttpResponseHeaders.cs | 12 +- src/Kestrel.Core/Internal/Http/IHttpParser.cs | 5 +- .../Internal/Http/IHttpProtocolContext.cs | 2 +- src/Kestrel.Core/Internal/Http/MessageBody.cs | 1 + .../Internal/Http/PipelineExtensions.cs | 12 +- src/Kestrel.Core/Internal/Http/UrlDecoder.cs | 411 ++++++++++++++++++ .../Internal/Http2/Http2Connection.cs | 2 +- .../Internal/Http2/Http2ConnectionContext.cs | 2 +- .../Internal/Http2/Http2FrameReader.cs | 3 +- .../Internal/Http2/Http2FrameWriter.cs | 1 - .../Internal/Http2/Http2Stream.cs | 9 +- .../Internal/Http2/Http2StreamContext.cs | 2 +- src/Kestrel.Core/Internal/HttpConnection.cs | 2 +- .../Internal/HttpConnectionContext.cs | 2 +- .../Infrastructure/KestrelThreadPool.cs | 1 + src/Kestrel.Core/Kestrel.Core.csproj | 1 + .../Internal/KestrelMemoryPool.cs | 12 + .../Internal/TransportConnection.Features.cs | 2 +- .../Internal/TransportConnection.cs | 2 +- .../Kestrel.Transport.Abstractions.csproj | 3 + .../Internal/LibuvConnection.cs | 6 +- .../Internal/LibuvConnectionContext.cs | 3 +- .../Internal/LibuvOutputConsumer.cs | 2 +- .../Internal/LibuvThread.cs | 6 +- .../Internal/Networking/UvWriteReq.cs | 4 +- .../Internal/SocketConnection.cs | 29 +- .../Internal/SocketSender.cs | 4 +- .../SocketTransport.cs | 2 +- src/Protocols.Abstractions/DuplexPipe.cs | 2 +- .../Features/IConnectionTransportFeature.cs | 2 +- .../Protocols.Abstractions.csproj | 1 - .../ConnectionHandlerTests.cs | 4 +- .../Http1ConnectionTests.cs | 7 +- .../Http2ConnectionTests.cs | 3 +- .../Kestrel.Core.Tests/HttpConnectionTests.cs | 5 +- test/Kestrel.Core.Tests/HttpParserTests.cs | 38 +- .../HttpProtocolFeatureCollectionTests.cs | 5 +- .../HttpResponseHeadersTests.cs | 3 +- .../Kestrel.Core.Tests/OutputProducerTests.cs | 5 +- test/Kestrel.Core.Tests/PipeOptionsTests.cs | 6 +- .../PipelineExtensionTests.cs | 23 +- test/Kestrel.Core.Tests/TestInput.cs | 9 +- .../LibuvOutputConsumerTests.cs | 6 +- .../MultipleLoopTests.cs | 2 +- .../NetworkingTests.cs | 2 +- .../TestHelpers/MockConnectionHandler.cs | 4 +- tools/CodeGenerator/KnownHeaders.cs | 2 +- 68 files changed, 715 insertions(+), 258 deletions(-) create mode 100644 src/Kestrel.Core/Internal/Http/UrlDecoder.cs create mode 100644 src/Kestrel.Transport.Abstractions/Internal/KestrelMemoryPool.cs diff --git a/Directory.Build.props b/Directory.Build.props index 170752b08d..ca80f126d5 100644 --- a/Directory.Build.props +++ b/Directory.Build.props @@ -17,5 +17,7 @@ true true latest + + false diff --git a/benchmarks/Kestrel.Performance/Http1ConnectionParsingOverheadBenchmark.cs b/benchmarks/Kestrel.Performance/Http1ConnectionParsingOverheadBenchmark.cs index ff3fa369bf..4d3f9ec311 100644 --- a/benchmarks/Kestrel.Performance/Http1ConnectionParsingOverheadBenchmark.cs +++ b/benchmarks/Kestrel.Performance/Http1ConnectionParsingOverheadBenchmark.cs @@ -9,6 +9,7 @@ using Microsoft.AspNetCore.Server.Kestrel.Core; using Microsoft.AspNetCore.Server.Kestrel.Core.Internal; using Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http; using Microsoft.AspNetCore.Server.Kestrel.Performance.Mocks; +using Microsoft.AspNetCore.Server.Kestrel.Transport.Abstractions.Internal; namespace Microsoft.AspNetCore.Server.Kestrel.Performance { @@ -16,13 +17,13 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Performance { private const int InnerLoopCount = 512; - public ReadOnlyBuffer _buffer; + public ReadOnlySequence _buffer; public Http1Connection _http1Connection; [IterationSetup] public void Setup() { - var memoryPool = new MemoryPool(); + var memoryPool = KestrelMemoryPool.Create(); var pair = DuplexPipe.CreateConnectionPair(memoryPool); var serviceContext = new ServiceContext diff --git a/benchmarks/Kestrel.Performance/Http1WritingBenchmark.cs b/benchmarks/Kestrel.Performance/Http1WritingBenchmark.cs index 1f00d1e6cf..d3137dac17 100644 --- a/benchmarks/Kestrel.Performance/Http1WritingBenchmark.cs +++ b/benchmarks/Kestrel.Performance/Http1WritingBenchmark.cs @@ -12,6 +12,7 @@ using Microsoft.AspNetCore.Http.Features; using Microsoft.AspNetCore.Server.Kestrel.Core; using Microsoft.AspNetCore.Server.Kestrel.Core.Internal; using Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http; +using Microsoft.AspNetCore.Server.Kestrel.Transport.Abstractions.Internal; using Microsoft.AspNetCore.Testing; namespace Microsoft.AspNetCore.Server.Kestrel.Performance @@ -24,15 +25,17 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Performance private static readonly Task _psuedoAsyncTask = Task.FromResult(27); private static readonly Func _psuedoAsyncTaskFunc = (obj) => _psuedoAsyncTask; - private readonly TestHttp1Connection _http1Connection; + private TestHttp1Connection _http1Connection; private DuplexPipe.DuplexPipePair _pair; + private MemoryPool _memoryPool; - private readonly byte[] _writeData; + private readonly byte[] _writeData = Encoding.ASCII.GetBytes("Hello, World!"); - public Http1WritingBenchmark() + [GlobalSetup] + public void GlobalSetup() { + _memoryPool = KestrelMemoryPool.Create(); _http1Connection = MakeHttp1Connection(); - _writeData = Encoding.ASCII.GetBytes("Hello, World!"); } [Params(true, false)] @@ -93,33 +96,30 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Performance private TestHttp1Connection MakeHttp1Connection() { - using (var memoryPool = new MemoryPool()) + var pair = DuplexPipe.CreateConnectionPair(_memoryPool); + _pair = pair; + + var serviceContext = new ServiceContext { - var pair = DuplexPipe.CreateConnectionPair(memoryPool); - _pair = pair; + DateHeaderValueManager = new DateHeaderValueManager(), + ServerOptions = new KestrelServerOptions(), + Log = new MockTrace(), + HttpParser = new HttpParser() + }; - var serviceContext = new ServiceContext - { - DateHeaderValueManager = new DateHeaderValueManager(), - ServerOptions = new KestrelServerOptions(), - Log = new MockTrace(), - HttpParser = new HttpParser() - }; + var http1Connection = new TestHttp1Connection(new Http1ConnectionContext + { + ServiceContext = serviceContext, + ConnectionFeatures = new FeatureCollection(), + MemoryPool = _memoryPool, + Application = pair.Application, + Transport = pair.Transport + }); - var http1Connection = new TestHttp1Connection(new Http1ConnectionContext - { - ServiceContext = serviceContext, - ConnectionFeatures = new FeatureCollection(), - MemoryPool = memoryPool, - Application = pair.Application, - Transport = pair.Transport - }); + http1Connection.Reset(); + http1Connection.InitializeStreams(MessageBody.ZeroContentLengthKeepAlive); - http1Connection.Reset(); - http1Connection.InitializeStreams(MessageBody.ZeroContentLengthKeepAlive); - - return http1Connection; - } + return http1Connection; } [IterationCleanup] @@ -138,5 +138,11 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Performance Sync, Async } + + [GlobalCleanup] + public void Dispose() + { + _memoryPool?.Dispose(); + } } } diff --git a/benchmarks/Kestrel.Performance/HttpParserBenchmark.cs b/benchmarks/Kestrel.Performance/HttpParserBenchmark.cs index 5b02d38a93..bb14ffb537 100644 --- a/benchmarks/Kestrel.Performance/HttpParserBenchmark.cs +++ b/benchmarks/Kestrel.Performance/HttpParserBenchmark.cs @@ -12,7 +12,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Performance { private readonly HttpParser _parser = new HttpParser(); - private ReadOnlyBuffer _buffer; + private ReadOnlySequence _buffer; [Benchmark(Baseline = true, OperationsPerInvoke = RequestParsingData.InnerLoopCount)] public void PlaintextTechEmpower() @@ -46,7 +46,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Performance private void InsertData(byte[] data) { - _buffer = new ReadOnlyBuffer(data); + _buffer = new ReadOnlySequence(data); } private void ParseData() diff --git a/benchmarks/Kestrel.Performance/HttpProtocolFeatureCollection.cs b/benchmarks/Kestrel.Performance/HttpProtocolFeatureCollection.cs index 69f07faf3e..081d17dc79 100644 --- a/benchmarks/Kestrel.Performance/HttpProtocolFeatureCollection.cs +++ b/benchmarks/Kestrel.Performance/HttpProtocolFeatureCollection.cs @@ -12,6 +12,7 @@ using Microsoft.AspNetCore.Http.Features; using Microsoft.AspNetCore.Server.Kestrel.Core; using Microsoft.AspNetCore.Server.Kestrel.Core.Internal; using Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http; +using Microsoft.AspNetCore.Server.Kestrel.Transport.Abstractions.Internal; namespace Microsoft.AspNetCore.Server.Kestrel.Performance { @@ -78,7 +79,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Performance public HttpProtocolFeatureCollection() { - var memoryPool = new MemoryPool(); + var memoryPool = KestrelMemoryPool.Create(); var pair = DuplexPipe.CreateConnectionPair(memoryPool); var serviceContext = new ServiceContext diff --git a/benchmarks/Kestrel.Performance/Mocks/NullParser.cs b/benchmarks/Kestrel.Performance/Mocks/NullParser.cs index 60bab155a0..99564a460b 100644 --- a/benchmarks/Kestrel.Performance/Mocks/NullParser.cs +++ b/benchmarks/Kestrel.Performance/Mocks/NullParser.cs @@ -22,7 +22,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Performance public static readonly NullParser Instance = new NullParser(); - public bool ParseHeaders(TRequestHandler handler, ReadOnlyBuffer buffer, out SequencePosition consumed, out SequencePosition examined, out int consumedBytes) + public bool ParseHeaders(TRequestHandler handler, ReadOnlySequence buffer, out SequencePosition consumed, out SequencePosition examined, out int consumedBytes) { handler.OnHeader(new Span(_hostHeaderName), new Span(_hostHeaderValue)); handler.OnHeader(new Span(_acceptHeaderName), new Span(_acceptHeaderValue)); @@ -35,7 +35,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Performance return true; } - public bool ParseRequestLine(TRequestHandler handler, ReadOnlyBuffer buffer, out SequencePosition consumed, out SequencePosition examined) + public bool ParseRequestLine(TRequestHandler handler, ReadOnlySequence buffer, out SequencePosition consumed, out SequencePosition examined) { handler.OnStartLine(HttpMethod.Get, HttpVersion.Http11, diff --git a/benchmarks/Kestrel.Performance/PipeThroughputBenchmark.cs b/benchmarks/Kestrel.Performance/PipeThroughputBenchmark.cs index 80e331ea5a..b647567715 100644 --- a/benchmarks/Kestrel.Performance/PipeThroughputBenchmark.cs +++ b/benchmarks/Kestrel.Performance/PipeThroughputBenchmark.cs @@ -1,10 +1,12 @@ // 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.Buffers; using System.IO.Pipelines; using System.Threading.Tasks; using BenchmarkDotNet.Attributes; +using Microsoft.AspNetCore.Server.Kestrel.Transport.Abstractions.Internal; namespace Microsoft.AspNetCore.Server.Kestrel.Performance { @@ -14,12 +16,12 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Performance private const int InnerLoopCount = 512; private Pipe _pipe; - private MemoryPool _memoryPool; + private MemoryPool _memoryPool; [IterationSetup] public void Setup() { - _memoryPool = new MemoryPool(); + _memoryPool = KestrelMemoryPool.Create(); _pipe = new Pipe(new PipeOptions(_memoryPool)); } @@ -62,5 +64,11 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Performance _pipe.Reader.AdvanceTo(result.Buffer.End, result.Buffer.End); } } + + [IterationCleanup] + public void Cleanup() + { + _memoryPool.Dispose(); + } } } diff --git a/benchmarks/Kestrel.Performance/RequestParsingBenchmark.cs b/benchmarks/Kestrel.Performance/RequestParsingBenchmark.cs index c682553483..e6ea256d01 100644 --- a/benchmarks/Kestrel.Performance/RequestParsingBenchmark.cs +++ b/benchmarks/Kestrel.Performance/RequestParsingBenchmark.cs @@ -9,11 +9,14 @@ using Microsoft.AspNetCore.Server.Kestrel.Core; using Microsoft.AspNetCore.Server.Kestrel.Core.Internal; using Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http; using Microsoft.AspNetCore.Server.Kestrel.Performance.Mocks; +using Microsoft.AspNetCore.Server.Kestrel.Transport.Abstractions.Internal; namespace Microsoft.AspNetCore.Server.Kestrel.Performance { public class RequestParsingBenchmark { + private MemoryPool _memoryPool; + public Pipe Pipe { get; set; } public Http1Connection Http1Connection { get; set; } @@ -21,8 +24,8 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Performance [IterationSetup] public void Setup() { - var memoryPool = new MemoryPool(); - var pair = DuplexPipe.CreateConnectionPair(memoryPool); + _memoryPool = KestrelMemoryPool.Create(); + var pair = DuplexPipe.CreateConnectionPair(_memoryPool); var serviceContext = new ServiceContext { @@ -36,7 +39,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Performance { ServiceContext = serviceContext, ConnectionFeatures = new FeatureCollection(), - MemoryPool = memoryPool, + MemoryPool = _memoryPool, Application = pair.Application, Transport = pair.Transport, TimeoutControl = new MockTimeoutControl() @@ -45,7 +48,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Performance http1Connection.Reset(); Http1Connection = http1Connection; - Pipe = new Pipe(new PipeOptions(memoryPool)); + Pipe = new Pipe(new PipeOptions(_memoryPool)); } [Benchmark(Baseline = true, OperationsPerInvoke = RequestParsingData.InnerLoopCount)] @@ -201,5 +204,12 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Performance } while (true); } + + + [IterationCleanup] + public void Cleanup() + { + _memoryPool.Dispose(); + } } } diff --git a/benchmarks/Kestrel.Performance/ResponseHeaderCollectionBenchmark.cs b/benchmarks/Kestrel.Performance/ResponseHeaderCollectionBenchmark.cs index dd6cd25cf4..b36f674ba4 100644 --- a/benchmarks/Kestrel.Performance/ResponseHeaderCollectionBenchmark.cs +++ b/benchmarks/Kestrel.Performance/ResponseHeaderCollectionBenchmark.cs @@ -12,6 +12,7 @@ using Microsoft.AspNetCore.Http.Internal; using Microsoft.AspNetCore.Server.Kestrel.Core; using Microsoft.AspNetCore.Server.Kestrel.Core.Internal; using Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http; +using Microsoft.AspNetCore.Server.Kestrel.Transport.Abstractions.Internal; namespace Microsoft.AspNetCore.Server.Kestrel.Performance { @@ -170,7 +171,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Performance [IterationSetup] public void Setup() { - var memoryPool = new MemoryPool(); + var memoryPool = KestrelMemoryPool.Create(); var pair = DuplexPipe.CreateConnectionPair(memoryPool); var serviceContext = new ServiceContext diff --git a/benchmarks/Kestrel.Performance/ResponseHeadersWritingBenchmark.cs b/benchmarks/Kestrel.Performance/ResponseHeadersWritingBenchmark.cs index 8c7c3b46be..67b3705238 100644 --- a/benchmarks/Kestrel.Performance/ResponseHeadersWritingBenchmark.cs +++ b/benchmarks/Kestrel.Performance/ResponseHeadersWritingBenchmark.cs @@ -13,6 +13,7 @@ using Microsoft.AspNetCore.Server.Kestrel.Core; using Microsoft.AspNetCore.Server.Kestrel.Core.Internal; using Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http; using Microsoft.AspNetCore.Server.Kestrel.Performance.Mocks; +using Microsoft.AspNetCore.Server.Kestrel.Transport.Abstractions.Internal; using Microsoft.AspNetCore.Testing; namespace Microsoft.AspNetCore.Server.Kestrel.Performance @@ -23,6 +24,8 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Performance private TestHttp1Connection _http1Connection; + private MemoryPool _memoryPool; + [Params( BenchmarkTypes.TechEmpowerPlaintext, BenchmarkTypes.PlaintextChunked, @@ -110,8 +113,8 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Performance [IterationSetup] public void Setup() { - var memoryPool = new MemoryPool(); - var pair = DuplexPipe.CreateConnectionPair(memoryPool); + _memoryPool = KestrelMemoryPool.Create(); + var pair = DuplexPipe.CreateConnectionPair(_memoryPool); var serviceContext = new ServiceContext { @@ -125,7 +128,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Performance { ServiceContext = serviceContext, ConnectionFeatures = new FeatureCollection(), - MemoryPool = memoryPool, + MemoryPool = _memoryPool, TimeoutControl = new MockTimeoutControl(), Application = pair.Application, Transport = pair.Transport @@ -136,6 +139,12 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Performance _http1Connection = http1Connection; } + [IterationCleanup] + public void Cleanup() + { + _memoryPool.Dispose(); + } + public enum BenchmarkTypes { TechEmpowerPlaintext, diff --git a/build/dependencies.props b/build/dependencies.props index b58d70b16c..f05fd787d5 100644 --- a/build/dependencies.props +++ b/build/dependencies.props @@ -5,40 +5,39 @@ 0.10.11 - 2.1.0-preview2-15721 + 2.1.0-preview2-15707 1.10.0 - 2.1.0-preview2-30187 - 2.1.0-preview2-30187 - 2.1.0-preview2-30187 - 2.1.0-preview2-30187 - 2.1.0-preview2-30187 - 2.1.0-preview2-30187 - 2.1.0-preview2-30187 - 2.1.0-preview2-30187 - 2.1.0-preview2-30187 - 2.1.0-preview2-30187 - 2.1.0-preview2-30187 - 2.1.0-preview2-30187 - 2.1.0-preview2-30187 - 2.1.0-preview2-30187 - 2.1.0-preview2-30187 - 2.1.0-preview2-30187 - 2.1.0-preview2-30187 + 2.1.0-preview2-30131 + 2.1.0-preview2-30131 + 2.1.0-preview2-30131 + 2.1.0-preview2-30131 + 2.1.0-preview2-30131 + 2.1.0-preview2-30131 + 2.1.0-preview2-30131 + 2.1.0-preview2-30131 + 2.1.0-preview2-30131 + 2.1.0-preview2-t000 + 2.1.0-preview2-30131 + 2.1.0-preview2-30131 + 2.1.0-preview2-30131 + 2.1.0-preview2-30131 + 2.1.0-preview2-30131 + 2.1.0-preview2-30131 + 2.1.0-preview2-30131 + 2.1.0-preview2-30131 2.0.0 - 2.1.0-preview2-26130-04 - 2.1.0-preview2-30187 - 15.6.0 + 2.1.0-preview2-26225-03 + 2.1.0-preview2-30131 + 15.3.0 4.7.49 10.0.1 - 4.5.0-preview2-26130-01 - 0.1.0-preview2-180130-1 - 0.1.0-preview2-180130-1 - 4.5.0-preview2-26130-01 - 4.5.0-preview2-26130-01 - 4.5.0-preview2-26130-01 - 4.5.0-preview2-26130-01 - 0.1.0-preview2-180130-1 - 4.5.0-preview2-26130-01 + 4.5.0-preview2-26224-02 + 4.5.0-preview2-26224-02 + 4.5.0-preview2-26224-02 + 4.5.0-preview2-26224-02 + 4.5.0-preview2-26224-02 + 4.5.0-preview2-26224-02 + 4.5.0-preview2-26224-02 0.8.0 2.3.1 2.4.0-beta.1.build3945 diff --git a/src/Kestrel.Core/Adapter/Internal/AdaptedPipeline.cs b/src/Kestrel.Core/Adapter/Internal/AdaptedPipeline.cs index 4204469241..522768e270 100644 --- a/src/Kestrel.Core/Adapter/Internal/AdaptedPipeline.cs +++ b/src/Kestrel.Core/Adapter/Internal/AdaptedPipeline.cs @@ -62,7 +62,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Adapter.Internal try { - if (result.IsCancelled) + if (result.IsCanceled) { // Forward the cancellation to the transport pipe _application.Input.CancelPendingRead(); @@ -132,26 +132,18 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Adapter.Internal { var outputBuffer = Input.Writer.GetMemory(MinAllocBufferSize); - - try - { #if NETCOREAPP2_1 - var bytesRead = await stream.ReadAsync(outputBuffer); + var bytesRead = await stream.ReadAsync(outputBuffer); #else - var array = outputBuffer.GetArray(); - var bytesRead = await stream.ReadAsync(array.Array, array.Offset, array.Count); + var array = outputBuffer.GetArray(); + var bytesRead = await stream.ReadAsync(array.Array, array.Offset, array.Count); #endif - Input.Writer.Advance(bytesRead); + Input.Writer.Advance(bytesRead); - if (bytesRead == 0) - { - // FIN - break; - } - } - finally + if (bytesRead == 0) { - Input.Writer.Commit(); + // FIN + break; } var result = await Input.Writer.FlushAsync(); @@ -160,7 +152,6 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Adapter.Internal { break; } - } } catch (Exception ex) diff --git a/src/Kestrel.Core/Internal/ConnectionHandler.cs b/src/Kestrel.Core/Internal/ConnectionHandler.cs index 41607602d4..8cd9552522 100644 --- a/src/Kestrel.Core/Internal/ConnectionHandler.cs +++ b/src/Kestrel.Core/Internal/ConnectionHandler.cs @@ -83,7 +83,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal } // Internal for testing - internal static PipeOptions GetInputPipeOptions(ServiceContext serviceContext, MemoryPool memoryPool, PipeScheduler writerScheduler) => new PipeOptions + internal static PipeOptions GetInputPipeOptions(ServiceContext serviceContext, MemoryPool memoryPool, PipeScheduler writerScheduler) => new PipeOptions ( pool: memoryPool, readerScheduler: serviceContext.ThreadPool, @@ -92,7 +92,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal resumeWriterThreshold: serviceContext.ServerOptions.Limits.MaxRequestBufferSize ?? 0 ); - internal static PipeOptions GetOutputPipeOptions(ServiceContext serviceContext, MemoryPool memoryPool, PipeScheduler readerScheduler) => new PipeOptions + internal static PipeOptions GetOutputPipeOptions(ServiceContext serviceContext, MemoryPool memoryPool, PipeScheduler readerScheduler) => new PipeOptions ( pool: memoryPool, readerScheduler: readerScheduler, diff --git a/src/Kestrel.Core/Internal/Http/ChunkWriter.cs b/src/Kestrel.Core/Internal/Http/ChunkWriter.cs index d37f6d48df..3d8cc4566b 100644 --- a/src/Kestrel.Core/Internal/Http/ChunkWriter.cs +++ b/src/Kestrel.Core/Internal/Http/ChunkWriter.cs @@ -8,7 +8,7 @@ using System.Text; namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http { - public static class ChunkWriter + internal static class ChunkWriter { private static readonly ArraySegment _endChunkBytes = CreateAsciiByteArraySegment("\r\n"); private static readonly byte[] _hex = Encoding.ASCII.GetBytes("0123456789abcdef"); @@ -48,14 +48,14 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http return new ArraySegment(bytes, offset, 10 - offset); } - public static int WriteBeginChunkBytes(ref OutputWriter start, int dataCount) + internal static int WriteBeginChunkBytes(ref BufferWriter start, int dataCount) { var chunkSegment = BeginChunkBytes(dataCount); start.Write(new ReadOnlySpan(chunkSegment.Array, chunkSegment.Offset, chunkSegment.Count)); return chunkSegment.Count; } - public static void WriteEndChunkBytes(ref OutputWriter start) + internal static void WriteEndChunkBytes(ref BufferWriter start) { start.Write(new ReadOnlySpan(_endChunkBytes.Array, _endChunkBytes.Offset, _endChunkBytes.Count)); } diff --git a/src/Kestrel.Core/Internal/Http/Http1Connection.cs b/src/Kestrel.Core/Internal/Http/Http1Connection.cs index 142902a8de..c8bab28d31 100644 --- a/src/Kestrel.Core/Internal/Http/Http1Connection.cs +++ b/src/Kestrel.Core/Internal/Http/Http1Connection.cs @@ -8,9 +8,10 @@ using System.Diagnostics; using System.IO.Pipelines; using System.Runtime.InteropServices; using System.Text; -using System.Text.Encodings.Web.Utf8; +using System.Text.Encodings.Web; using System.Threading; using Microsoft.AspNetCore.Http.Features; +using Microsoft.AspNetCore.Protocols.Abstractions; using Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Infrastructure; namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http @@ -69,7 +70,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http Input.CancelPendingRead(); } - public void ParseRequest(ReadOnlyBuffer buffer, out SequencePosition consumed, out SequencePosition examined) + public void ParseRequest(ReadOnlySequence buffer, out SequencePosition consumed, out SequencePosition examined) { consumed = buffer.Start; examined = buffer.End; @@ -107,7 +108,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http } } - public bool TakeStartLine(ReadOnlyBuffer buffer, out SequencePosition consumed, out SequencePosition examined) + public bool TakeStartLine(ReadOnlySequence buffer, out SequencePosition consumed, out SequencePosition examined) { var overLength = false; if (buffer.Length >= ServerOptions.Limits.MaxRequestLineSize) @@ -125,7 +126,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http return result; } - public bool TakeMessageHeaders(ReadOnlyBuffer buffer, out SequencePosition consumed, out SequencePosition examined) + public bool TakeMessageHeaders(ReadOnlySequence buffer, out SequencePosition consumed, out SequencePosition examined) { // Make sure the buffer is limited bool overLength = false; @@ -217,9 +218,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http { // URI was encoded, unescape and then parse as UTF-8 // Disabling warning temporary -#pragma warning disable 618 - var pathLength = UrlEncoder.Decode(path, path); -#pragma warning restore 618 + var pathLength = UrlDecoder.Decode(path, path); // Removing dot segments must be done after unescaping. From RFC 3986: // @@ -430,7 +429,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http TimeoutControl.SetTimeout(_keepAliveTicks, TimeoutAction.StopProcessingNextRequest); } - protected override bool BeginRead(out ValueAwaiter awaitable) + protected override bool BeginRead(out PipeAwaiter awaitable) { awaitable = Input.ReadAsync(); return true; diff --git a/src/Kestrel.Core/Internal/Http/Http1ConnectionContext.cs b/src/Kestrel.Core/Internal/Http/Http1ConnectionContext.cs index b88bb4bedb..9dbcee29e7 100644 --- a/src/Kestrel.Core/Internal/Http/Http1ConnectionContext.cs +++ b/src/Kestrel.Core/Internal/Http/Http1ConnectionContext.cs @@ -14,7 +14,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http public string ConnectionId { get; set; } public ServiceContext ServiceContext { get; set; } public IFeatureCollection ConnectionFeatures { get; set; } - public MemoryPool MemoryPool { get; set; } + public MemoryPool MemoryPool { get; set; } public IPEndPoint RemoteEndPoint { get; set; } public IPEndPoint LocalEndPoint { get; set; } public ITimeoutControl TimeoutControl { get; set; } diff --git a/src/Kestrel.Core/Internal/Http/Http1MessageBody.cs b/src/Kestrel.Core/Internal/Http/Http1MessageBody.cs index 4eb3f8928d..1e7f3caf2d 100644 --- a/src/Kestrel.Core/Internal/Http/Http1MessageBody.cs +++ b/src/Kestrel.Core/Internal/Http/Http1MessageBody.cs @@ -8,6 +8,7 @@ using System.IO; using System.IO.Pipelines; using System.Threading.Tasks; using Microsoft.AspNetCore.Http; +using Microsoft.AspNetCore.Protocols.Abstractions; using Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Infrastructure; namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http @@ -63,15 +64,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http if (!readableBuffer.IsEmpty) { bool done; - - try - { - done = Read(readableBuffer, _context.RequestBodyPipe.Writer, out consumed, out examined); - } - finally - { - _context.RequestBodyPipe.Writer.Commit(); - } + done = Read(readableBuffer, _context.RequestBodyPipe.Writer, out consumed, out examined); var writeAwaitable = _context.RequestBodyPipe.Writer.FlushAsync(); var backpressure = false; @@ -150,7 +143,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http } } - protected void Copy(ReadOnlyBuffer readableBuffer, PipeWriter writableBuffer) + protected void Copy(ReadOnlySequence readableBuffer, PipeWriter writableBuffer) { _context.TimeoutControl.BytesRead(readableBuffer.Length); @@ -172,7 +165,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http _pumpTask = PumpAsync(); } - protected virtual bool Read(ReadOnlyBuffer readableBuffer, PipeWriter writableBuffer, out SequencePosition consumed, out SequencePosition examined) + protected virtual bool Read(ReadOnlySequence readableBuffer, PipeWriter writableBuffer, out SequencePosition consumed, out SequencePosition examined) { throw new NotImplementedException(); } @@ -295,7 +288,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http public override bool IsEmpty => true; - protected override bool Read(ReadOnlyBuffer readableBuffer, PipeWriter writableBuffer, out SequencePosition consumed, out SequencePosition examined) + protected override bool Read(ReadOnlySequence readableBuffer, PipeWriter writableBuffer, out SequencePosition consumed, out SequencePosition examined) { Copy(readableBuffer, writableBuffer); consumed = readableBuffer.End; @@ -317,7 +310,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http _inputLength = _contentLength; } - protected override bool Read(ReadOnlyBuffer readableBuffer, PipeWriter writableBuffer, out SequencePosition consumed, out SequencePosition examined) + protected override bool Read(ReadOnlySequence readableBuffer, PipeWriter writableBuffer, out SequencePosition consumed, out SequencePosition examined) { if (_inputLength == 0) { @@ -365,7 +358,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http RequestKeepAlive = keepAlive; } - protected override bool Read(ReadOnlyBuffer readableBuffer, PipeWriter writableBuffer, out SequencePosition consumed, out SequencePosition examined) + protected override bool Read(ReadOnlySequence readableBuffer, PipeWriter writableBuffer, out SequencePosition consumed, out SequencePosition examined) { consumed = default(SequencePosition); examined = default(SequencePosition); @@ -456,11 +449,11 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http } } - private void ParseChunkedPrefix(ReadOnlyBuffer buffer, out SequencePosition consumed, out SequencePosition examined) + private void ParseChunkedPrefix(ReadOnlySequence buffer, out SequencePosition consumed, out SequencePosition examined) { consumed = buffer.Start; examined = buffer.Start; - var reader = BufferReader.Create(buffer); + var reader = new BufferReader(buffer); var ch1 = reader.Read(); var ch2 = reader.Read(); @@ -512,7 +505,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http BadHttpRequestException.Throw(RequestRejectionReason.BadChunkSizeData); } - private void ParseExtension(ReadOnlyBuffer buffer, out SequencePosition consumed, out SequencePosition examined) + private void ParseExtension(ReadOnlySequence buffer, out SequencePosition consumed, out SequencePosition examined) { // Chunk-extensions not currently parsed // Just drain the data @@ -565,7 +558,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http } while (_mode == Mode.Extension); } - private void ReadChunkedData(ReadOnlyBuffer buffer, PipeWriter writableBuffer, out SequencePosition consumed, out SequencePosition examined) + private void ReadChunkedData(ReadOnlySequence buffer, PipeWriter writableBuffer, out SequencePosition consumed, out SequencePosition examined) { var actual = Math.Min(buffer.Length, _inputLength); consumed = buffer.GetPosition(buffer.Start, actual); @@ -582,7 +575,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http } } - private void ParseChunkedSuffix(ReadOnlyBuffer buffer, out SequencePosition consumed, out SequencePosition examined) + private void ParseChunkedSuffix(ReadOnlySequence buffer, out SequencePosition consumed, out SequencePosition examined) { consumed = buffer.Start; examined = buffer.Start; @@ -608,7 +601,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http } } - private void ParseChunkedTrailer(ReadOnlyBuffer buffer, out SequencePosition consumed, out SequencePosition examined) + private void ParseChunkedTrailer(ReadOnlySequence buffer, out SequencePosition consumed, out SequencePosition examined) { consumed = buffer.Start; examined = buffer.Start; diff --git a/src/Kestrel.Core/Internal/Http/Http1OutputProducer.cs b/src/Kestrel.Core/Internal/Http/Http1OutputProducer.cs index dbc8cf0f52..3ecbf9298f 100644 --- a/src/Kestrel.Core/Internal/Http/Http1OutputProducer.cs +++ b/src/Kestrel.Core/Internal/Http/Http1OutputProducer.cs @@ -84,7 +84,6 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http var buffer = _pipeWriter; callback(buffer, state); - buffer.Commit(); } } @@ -99,7 +98,6 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http var buffer = _pipeWriter; callback(buffer, state); - buffer.Commit(); } return FlushAsync(); @@ -115,14 +113,14 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http } var buffer = _pipeWriter; - var writer = OutputWriter.Create(buffer); + var writer = new BufferWriter(buffer); writer.Write(_bytesHttpVersion11); var statusBytes = ReasonPhrases.ToStatusBytes(statusCode, reasonPhrase); writer.Write(statusBytes); responseHeaders.CopyTo(ref writer); writer.Write(_bytesEndHeaders); - buffer.Commit(); + writer.Commit(); } } @@ -177,14 +175,13 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http } writableBuffer = _pipeWriter; - var writer = OutputWriter.Create(writableBuffer); + var writer = new BufferWriter(writableBuffer); if (buffer.Length > 0) { writer.Write(buffer); bytesWritten += buffer.Length; } - - writableBuffer.Commit(); + writer.Commit(); } return FlushAsync(writableBuffer, bytesWritten, cancellationToken); @@ -203,7 +200,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http return FlushAsyncAwaited(awaitable, bytesWritten, cancellationToken); } - private async Task FlushAsyncAwaited(ValueAwaiter awaitable, long count, CancellationToken cancellationToken) + private async Task FlushAsyncAwaited(PipeAwaiter awaitable, long count, CancellationToken cancellationToken) { // https://github.com/dotnet/corefxlab/issues/1334 // Since the flush awaitable doesn't currently support multiple awaiters diff --git a/src/Kestrel.Core/Internal/Http/HttpHeaders.Generated.cs b/src/Kestrel.Core/Internal/Http/HttpHeaders.Generated.cs index 704d2216a8..84dc60f624 100644 --- a/src/Kestrel.Core/Internal/Http/HttpHeaders.Generated.cs +++ b/src/Kestrel.Core/Internal/Http/HttpHeaders.Generated.cs @@ -7754,7 +7754,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http return true; } - protected void CopyToFast(ref OutputWriter output) + internal void CopyToFast(ref BufferWriter output) { var tempBits = _bits | (_contentLength.HasValue ? -9223372036854775808L : 0); diff --git a/src/Kestrel.Core/Internal/Http/HttpParser.cs b/src/Kestrel.Core/Internal/Http/HttpParser.cs index 2ca5cdddbf..976841466a 100644 --- a/src/Kestrel.Core/Internal/Http/HttpParser.cs +++ b/src/Kestrel.Core/Internal/Http/HttpParser.cs @@ -7,6 +7,7 @@ using System.Collections; using System.Diagnostics; using System.Runtime.CompilerServices; using System.Runtime.InteropServices; +using Microsoft.AspNetCore.Protocols.Abstractions; using Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Infrastructure; namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http @@ -33,7 +34,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http private const byte ByteQuestionMark = (byte)'?'; private const byte BytePercentage = (byte)'%'; - public unsafe bool ParseRequestLine(TRequestHandler handler, ReadOnlyBuffer buffer, out SequencePosition consumed, out SequencePosition examined) + public unsafe bool ParseRequestLine(TRequestHandler handler, ReadOnlySequence buffer, out SequencePosition consumed, out SequencePosition examined) { consumed = buffer.Start; examined = buffer.End; @@ -188,7 +189,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http handler.OnStartLine(method, httpVersion, targetBuffer, pathBuffer, query, customMethod, pathEncoded); } - public unsafe bool ParseHeaders(TRequestHandler handler, ReadOnlyBuffer buffer, out SequencePosition consumed, out SequencePosition examined, out int consumedBytes) + public unsafe bool ParseHeaders(TRequestHandler handler, ReadOnlySequence buffer, out SequencePosition consumed, out SequencePosition examined, out int consumedBytes) { consumed = buffer.Start; examined = buffer.End; @@ -196,8 +197,8 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http var bufferEnd = buffer.End; - var reader = BufferReader.Create(buffer); - var start = default(BufferReader>); + var reader = new BufferReader(buffer); + var start = default(BufferReader); var done = false; try @@ -418,7 +419,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http } [MethodImpl(MethodImplOptions.NoInlining)] - private static bool TryGetNewLine(ref ReadOnlyBuffer buffer, out SequencePosition found) + private static bool TryGetNewLine(ref ReadOnlySequence buffer, out SequencePosition found) { var byteLfPosition = buffer.PositionOf(ByteLF); if (byteLfPosition != null) diff --git a/src/Kestrel.Core/Internal/Http/HttpProtocol.cs b/src/Kestrel.Core/Internal/Http/HttpProtocol.cs index 7839a33cbd..ad90b76a1e 100644 --- a/src/Kestrel.Core/Internal/Http/HttpProtocol.cs +++ b/src/Kestrel.Core/Internal/Http/HttpProtocol.cs @@ -383,7 +383,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http { } - protected virtual bool BeginRead(out ValueAwaiter awaitable) + protected virtual bool BeginRead(out PipeAwaiter awaitable) { awaitable = default; return false; @@ -930,12 +930,13 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http private static void WriteChunk(PipeWriter writableBuffer, ReadOnlyMemory buffer) { - var writer = OutputWriter.Create(writableBuffer); + var writer = new BufferWriter(writableBuffer); if (buffer.Length > 0) { ChunkWriter.WriteBeginChunkBytes(ref writer, buffer.Length); writer.Write(buffer.Span); ChunkWriter.WriteEndChunkBytes(ref writer); + writer.Commit(); } } diff --git a/src/Kestrel.Core/Internal/Http/HttpResponseHeaders.cs b/src/Kestrel.Core/Internal/Http/HttpResponseHeaders.cs index 9a75ff780a..badb63449c 100644 --- a/src/Kestrel.Core/Internal/Http/HttpResponseHeaders.cs +++ b/src/Kestrel.Core/Internal/Http/HttpResponseHeaders.cs @@ -35,9 +35,9 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http return GetEnumerator(); } - public void CopyTo(ref OutputWriter output) + internal void CopyTo(ref BufferWriter buffer) { - CopyToFast(ref output); + CopyToFast(ref buffer); if (MaybeUnknown != null) { foreach (var kv in MaybeUnknown) @@ -46,10 +46,10 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http { if (value != null) { - output.Write(_CrLf); - PipelineExtensions.WriteAsciiNoValidation(ref output, kv.Key); - output.Write(_colonSpace); - PipelineExtensions.WriteAsciiNoValidation(ref output, value); + buffer.Write(_CrLf); + PipelineExtensions.WriteAsciiNoValidation(ref buffer, kv.Key); + buffer.Write(_colonSpace); + PipelineExtensions.WriteAsciiNoValidation(ref buffer, value); } } } diff --git a/src/Kestrel.Core/Internal/Http/IHttpParser.cs b/src/Kestrel.Core/Internal/Http/IHttpParser.cs index e1978ffb01..2cd77a3cd1 100644 --- a/src/Kestrel.Core/Internal/Http/IHttpParser.cs +++ b/src/Kestrel.Core/Internal/Http/IHttpParser.cs @@ -1,6 +1,7 @@ // 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.Buffers; using System.Collections; @@ -8,8 +9,8 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http { public interface IHttpParser where TRequestHandler : IHttpHeadersHandler, IHttpRequestLineHandler { - bool ParseRequestLine(TRequestHandler handler, ReadOnlyBuffer buffer, out SequencePosition consumed, out SequencePosition examined); + bool ParseRequestLine(TRequestHandler handler, ReadOnlySequence buffer, out SequencePosition consumed, out SequencePosition examined); - bool ParseHeaders(TRequestHandler handler, ReadOnlyBuffer buffer, out SequencePosition consumed, out SequencePosition examined, out int consumedBytes); + bool ParseHeaders(TRequestHandler handler, ReadOnlySequence buffer, out SequencePosition consumed, out SequencePosition examined, out int consumedBytes); } } diff --git a/src/Kestrel.Core/Internal/Http/IHttpProtocolContext.cs b/src/Kestrel.Core/Internal/Http/IHttpProtocolContext.cs index c58d97196d..2aa526b867 100644 --- a/src/Kestrel.Core/Internal/Http/IHttpProtocolContext.cs +++ b/src/Kestrel.Core/Internal/Http/IHttpProtocolContext.cs @@ -12,7 +12,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http string ConnectionId { get; set; } ServiceContext ServiceContext { get; set; } IFeatureCollection ConnectionFeatures { get; set; } - MemoryPool MemoryPool { get; set; } + MemoryPool MemoryPool { get; set; } IPEndPoint RemoteEndPoint { get; set; } IPEndPoint LocalEndPoint { get; set; } } diff --git a/src/Kestrel.Core/Internal/Http/MessageBody.cs b/src/Kestrel.Core/Internal/Http/MessageBody.cs index ad330f2c92..2ebf6f1212 100644 --- a/src/Kestrel.Core/Internal/Http/MessageBody.cs +++ b/src/Kestrel.Core/Internal/Http/MessageBody.cs @@ -2,6 +2,7 @@ // Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. using System; +using System.Buffers; using System.IO; using System.Threading; using System.Threading.Tasks; diff --git a/src/Kestrel.Core/Internal/Http/PipelineExtensions.cs b/src/Kestrel.Core/Internal/Http/PipelineExtensions.cs index 4caefd3a95..b5a2664bda 100644 --- a/src/Kestrel.Core/Internal/Http/PipelineExtensions.cs +++ b/src/Kestrel.Core/Internal/Http/PipelineExtensions.cs @@ -17,7 +17,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http private static byte[] _numericBytesScratch; [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static ReadOnlySpan ToSpan(this ReadOnlyBuffer buffer) + public static ReadOnlySpan ToSpan(this ReadOnlySequence buffer) { if (buffer.IsSingleSegment) { @@ -45,7 +45,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http return result; } - public unsafe static void WriteAsciiNoValidation(ref this OutputWriter buffer, string data) + internal static unsafe void WriteAsciiNoValidation(ref this BufferWriter buffer, string data) { if (string.IsNullOrEmpty(data)) { @@ -74,7 +74,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http } [MethodImpl(MethodImplOptions.AggressiveInlining)] - public unsafe static void WriteNumeric(ref this OutputWriter buffer, ulong number) + internal static unsafe void WriteNumeric(ref this BufferWriter buffer, ulong number) { const byte AsciiDigitStart = (byte)'0'; @@ -124,7 +124,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http } [MethodImpl(MethodImplOptions.NoInlining)] - private static void WriteNumericMultiWrite(ref this OutputWriter buffer, ulong number) + private static void WriteNumericMultiWrite(ref this BufferWriter buffer, ulong number) { const byte AsciiDigitStart = (byte)'0'; @@ -145,7 +145,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http } [MethodImpl(MethodImplOptions.NoInlining)] - private unsafe static void WriteAsciiMultiWrite(ref this OutputWriter buffer, string data) + private unsafe static void WriteAsciiMultiWrite(ref this BufferWriter buffer, string data) { var remaining = data.Length; @@ -176,7 +176,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http } } - private unsafe static void EncodeAsciiCharsToBytes(char* input, byte* output, int length) + private static unsafe void EncodeAsciiCharsToBytes(char* input, byte* output, int length) { // Note: Not BIGENDIAN or check for non-ascii const int Shift16Shift24 = (1 << 16) | (1 << 24); diff --git a/src/Kestrel.Core/Internal/Http/UrlDecoder.cs b/src/Kestrel.Core/Internal/Http/UrlDecoder.cs new file mode 100644 index 0000000000..fcdb3cfef6 --- /dev/null +++ b/src/Kestrel.Core/Internal/Http/UrlDecoder.cs @@ -0,0 +1,411 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +using System; + +namespace Microsoft.AspNetCore.Protocols.Abstractions +{ + internal class UrlDecoder + { + static bool[] IsAllowed = new bool[0x7F + 1]; + + /// + /// Unescape a URL path + /// + /// The byte span represents a UTF8 encoding url path. + /// The byte span where unescaped url path is copied to. + /// The length of the byte sequence of the unescaped url path. + public static int Decode(ReadOnlySpan source, Span destination) + { + if (destination.Length < source.Length) + { + throw new ArgumentException( + "Lenghth of the destination byte span is less then the source.", + nameof(destination)); + } + + // This requires the destination span to be larger or equal to source span + source.CopyTo(destination); + return DecodeInPlace(destination); + } + + /// + /// Unescape a URL path in place. + /// + /// The byte span represents a UTF8 encoding url path. + /// The number of the bytes representing the result. + /// + /// The unescape is done in place, which means after decoding the result is the subset of + /// the input span. + /// + public static int DecodeInPlace(Span buffer) + { + // the slot to read the input + var sourceIndex = 0; + + // the slot to write the unescaped byte + var destinationIndex = 0; + + while (true) + { + if (sourceIndex == buffer.Length) + { + break; + } + + if (buffer[sourceIndex] == '%') + { + var decodeIndex = sourceIndex; + + // If decoding process succeeds, the writer iterator will be moved + // to the next write-ready location. On the other hand if the scanned + // percent-encodings cannot be interpreted as sequence of UTF-8 octets, + // these bytes should be copied to output as is. + // The decodeReader iterator is always moved to the first byte not yet + // be scanned after the process. A failed decoding means the chars + // between the reader and decodeReader can be copied to output untouched. + if (!DecodeCore(ref decodeIndex, ref destinationIndex, buffer)) + { + Copy(sourceIndex, decodeIndex, ref destinationIndex, buffer); + } + + sourceIndex = decodeIndex; + } + else + { + buffer[destinationIndex++] = buffer[sourceIndex++]; + } + } + + return destinationIndex; + } + + /// + /// Unescape the percent-encodings + /// + /// The iterator point to the first % char + /// The place to write to + /// The byte array + private static bool DecodeCore(ref int sourceIndex, ref int destinationIndex, Span buffer) + { + // preserves the original head. if the percent-encodings cannot be interpreted as sequence of UTF-8 octets, + // bytes from this till the last scanned one will be copied to the memory pointed by writer. + var byte1 = UnescapePercentEncoding(ref sourceIndex, buffer); + if (byte1 == -1) + { + return false; + } + + if (byte1 == 0) + { + throw new InvalidOperationException("The path contains null characters."); + } + + if (byte1 <= 0x7F) + { + // first byte < U+007f, it is a single byte ASCII + buffer[destinationIndex++] = (byte)byte1; + return true; + } + + int byte2 = 0, byte3 = 0, byte4 = 0; + + // anticipate more bytes + var currentDecodeBits = 0; + var byteCount = 1; + var expectValueMin = 0; + if ((byte1 & 0xE0) == 0xC0) + { + // 110x xxxx, expect one more byte + currentDecodeBits = byte1 & 0x1F; + byteCount = 2; + expectValueMin = 0x80; + } + else if ((byte1 & 0xF0) == 0xE0) + { + // 1110 xxxx, expect two more bytes + currentDecodeBits = byte1 & 0x0F; + byteCount = 3; + expectValueMin = 0x800; + } + else if ((byte1 & 0xF8) == 0xF0) + { + // 1111 0xxx, expect three more bytes + currentDecodeBits = byte1 & 0x07; + byteCount = 4; + expectValueMin = 0x10000; + } + else + { + // invalid first byte + return false; + } + + var remainingBytes = byteCount - 1; + while (remainingBytes > 0) + { + // read following three chars + if (sourceIndex == buffer.Length) + { + return false; + } + + var nextSourceIndex = sourceIndex; + var nextByte = UnescapePercentEncoding(ref nextSourceIndex, buffer); + if (nextByte == -1) + { + return false; + } + + if ((nextByte & 0xC0) != 0x80) + { + // the follow up byte is not in form of 10xx xxxx + return false; + } + + currentDecodeBits = (currentDecodeBits << 6) | (nextByte & 0x3F); + remainingBytes--; + + if (remainingBytes == 1 && currentDecodeBits >= 0x360 && currentDecodeBits <= 0x37F) + { + // this is going to end up in the range of 0xD800-0xDFFF UTF-16 surrogates that + // are not allowed in UTF-8; + return false; + } + + if (remainingBytes == 2 && currentDecodeBits >= 0x110) + { + // this is going to be out of the upper Unicode bound 0x10FFFF. + return false; + } + + sourceIndex = nextSourceIndex; + if (byteCount - remainingBytes == 2) + { + byte2 = nextByte; + } + else if (byteCount - remainingBytes == 3) + { + byte3 = nextByte; + } + else if (byteCount - remainingBytes == 4) + { + byte4 = nextByte; + } + } + + if (currentDecodeBits < expectValueMin) + { + // overlong encoding (e.g. using 2 bytes to encode something that only needed 1). + return false; + } + + // all bytes are verified, write to the output + // TODO: measure later to determine if the performance of following logic can be improved + // the idea is to combine the bytes into short/int and write to span directly to avoid + // range check cost + if (byteCount > 0) + { + buffer[destinationIndex++] = (byte)byte1; + } + if (byteCount > 1) + { + buffer[destinationIndex++] = (byte)byte2; + } + if (byteCount > 2) + { + buffer[destinationIndex++] = (byte)byte3; + } + if (byteCount > 3) + { + buffer[destinationIndex++] = (byte)byte4; + } + + return true; + } + + private static void Copy(int begin, int end, ref int writer, Span buffer) + { + while (begin != end) + { + buffer[writer++] = buffer[begin++]; + } + } + + /// + /// Read the percent-encoding and try unescape it. + /// + /// The operation first peek at the character the + /// iterator points at. If it is % the is then + /// moved on to scan the following to characters. If the two following + /// characters are hexadecimal literals they will be unescaped and the + /// value will be returned. + /// + /// If the first character is not % the iterator + /// will be removed beyond the location of % and -1 will be returned. + /// + /// If the following two characters can't be successfully unescaped the + /// iterator will be move behind the % and -1 + /// will be returned. + /// + /// The value to read + /// The byte array + /// The unescaped byte if success. Otherwise return -1. + private static int UnescapePercentEncoding(ref int scan, Span buffer) + { + if (buffer[scan++] != '%') + { + return -1; + } + + var probe = scan; + + var value1 = ReadHex(ref probe, buffer); + if (value1 == -1) + { + return -1; + } + + var value2 = ReadHex(ref probe, buffer); + if (value2 == -1) + { + return -1; + } + + if (SkipUnescape(value1, value2)) + { + return -1; + } + + scan = probe; + return (value1 << 4) + value2; + } + + + /// + /// Read the next char and convert it into hexadecimal value. + /// + /// The index will be moved to the next + /// byte no matter no matter whether the operation successes. + /// + /// The index of the byte in the buffer to read + /// The byte span from which the hex to be read + /// The hexadecimal value if successes, otherwise -1. + private static int ReadHex(ref int scan, Span buffer) + { + if (scan == buffer.Length) + { + return -1; + } + + var value = buffer[scan++]; + var isHead = ((value >= '0') && (value <= '9')) || + ((value >= 'A') && (value <= 'F')) || + ((value >= 'a') && (value <= 'f')); + + if (!isHead) + { + return -1; + } + + if (value <= '9') + { + return value - '0'; + } + else if (value <= 'F') + { + return (value - 'A') + 10; + } + else // a - f + { + return (value - 'a') + 10; + } + } + + private static bool SkipUnescape(int value1, int value2) + { + // skip %2F - '/' + if (value1 == 2 && value2 == 15) + { + return true; + } + + return false; + } + + static UrlDecoder() + { + // Unreserved + IsAllowed['A'] = true; + IsAllowed['B'] = true; + IsAllowed['C'] = true; + IsAllowed['D'] = true; + IsAllowed['E'] = true; + IsAllowed['F'] = true; + IsAllowed['G'] = true; + IsAllowed['H'] = true; + IsAllowed['I'] = true; + IsAllowed['J'] = true; + IsAllowed['K'] = true; + IsAllowed['L'] = true; + IsAllowed['M'] = true; + IsAllowed['N'] = true; + IsAllowed['O'] = true; + IsAllowed['P'] = true; + IsAllowed['Q'] = true; + IsAllowed['R'] = true; + IsAllowed['S'] = true; + IsAllowed['T'] = true; + IsAllowed['U'] = true; + IsAllowed['V'] = true; + IsAllowed['W'] = true; + IsAllowed['X'] = true; + IsAllowed['Y'] = true; + IsAllowed['Z'] = true; + + IsAllowed['a'] = true; + IsAllowed['b'] = true; + IsAllowed['c'] = true; + IsAllowed['d'] = true; + IsAllowed['e'] = true; + IsAllowed['f'] = true; + IsAllowed['g'] = true; + IsAllowed['h'] = true; + IsAllowed['i'] = true; + IsAllowed['j'] = true; + IsAllowed['k'] = true; + IsAllowed['l'] = true; + IsAllowed['m'] = true; + IsAllowed['n'] = true; + IsAllowed['o'] = true; + IsAllowed['p'] = true; + IsAllowed['q'] = true; + IsAllowed['r'] = true; + IsAllowed['s'] = true; + IsAllowed['t'] = true; + IsAllowed['u'] = true; + IsAllowed['v'] = true; + IsAllowed['w'] = true; + IsAllowed['x'] = true; + IsAllowed['y'] = true; + IsAllowed['z'] = true; + + IsAllowed['0'] = true; + IsAllowed['1'] = true; + IsAllowed['2'] = true; + IsAllowed['3'] = true; + IsAllowed['4'] = true; + IsAllowed['5'] = true; + IsAllowed['6'] = true; + IsAllowed['7'] = true; + IsAllowed['8'] = true; + IsAllowed['9'] = true; + + IsAllowed['-'] = true; + IsAllowed['_'] = true; + IsAllowed['.'] = true; + IsAllowed['~'] = true; + } + } +} diff --git a/src/Kestrel.Core/Internal/Http2/Http2Connection.cs b/src/Kestrel.Core/Internal/Http2/Http2Connection.cs index 2d8fcd9b95..d0b46039db 100644 --- a/src/Kestrel.Core/Internal/Http2/Http2Connection.cs +++ b/src/Kestrel.Core/Internal/Http2/Http2Connection.cs @@ -215,7 +215,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http2 } } - private bool ParsePreface(ReadOnlyBuffer readableBuffer, out SequencePosition consumed, out SequencePosition examined) + private bool ParsePreface(ReadOnlySequence readableBuffer, out SequencePosition consumed, out SequencePosition examined) { consumed = readableBuffer.Start; examined = readableBuffer.End; diff --git a/src/Kestrel.Core/Internal/Http2/Http2ConnectionContext.cs b/src/Kestrel.Core/Internal/Http2/Http2ConnectionContext.cs index 73e4217a5c..ae9ceb3b70 100644 --- a/src/Kestrel.Core/Internal/Http2/Http2ConnectionContext.cs +++ b/src/Kestrel.Core/Internal/Http2/Http2ConnectionContext.cs @@ -13,7 +13,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http2 public string ConnectionId { get; set; } public ServiceContext ServiceContext { get; set; } public IFeatureCollection ConnectionFeatures { get; set; } - public MemoryPool MemoryPool { get; set; } + public MemoryPool MemoryPool { get; set; } public IPEndPoint LocalEndPoint { get; set; } public IPEndPoint RemoteEndPoint { get; set; } diff --git a/src/Kestrel.Core/Internal/Http2/Http2FrameReader.cs b/src/Kestrel.Core/Internal/Http2/Http2FrameReader.cs index 68bf0e07e2..b9d84fc896 100644 --- a/src/Kestrel.Core/Internal/Http2/Http2FrameReader.cs +++ b/src/Kestrel.Core/Internal/Http2/Http2FrameReader.cs @@ -1,6 +1,7 @@ // 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.Buffers; using System.Collections; @@ -8,7 +9,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http2 { public static class Http2FrameReader { - public static bool ReadFrame(ReadOnlyBuffer readableBuffer, Http2Frame frame, out SequencePosition consumed, out SequencePosition examined) + public static bool ReadFrame(ReadOnlySequence readableBuffer, Http2Frame frame, out SequencePosition consumed, out SequencePosition examined) { consumed = readableBuffer.Start; examined = readableBuffer.End; diff --git a/src/Kestrel.Core/Internal/Http2/Http2FrameWriter.cs b/src/Kestrel.Core/Internal/Http2/Http2FrameWriter.cs index 362bdc9759..e31d4b7f4f 100644 --- a/src/Kestrel.Core/Internal/Http2/Http2FrameWriter.cs +++ b/src/Kestrel.Core/Internal/Http2/Http2FrameWriter.cs @@ -190,7 +190,6 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http2 } _outputWriter.Write(data); - _outputWriter.Commit(); } // Must be called with _writeLock diff --git a/src/Kestrel.Core/Internal/Http2/Http2Stream.cs b/src/Kestrel.Core/Internal/Http2/Http2Stream.cs index de212d1933..41e84805d9 100644 --- a/src/Kestrel.Core/Internal/Http2/Http2Stream.cs +++ b/src/Kestrel.Core/Internal/Http2/Http2Stream.cs @@ -83,14 +83,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http2 { if (data.Count > 0) { - try - { - RequestBodyPipe.Writer.Write(data); - } - finally - { - RequestBodyPipe.Writer.Commit(); - } + RequestBodyPipe.Writer.Write(data); RequestBodyStarted = true; await RequestBodyPipe.Writer.FlushAsync(); diff --git a/src/Kestrel.Core/Internal/Http2/Http2StreamContext.cs b/src/Kestrel.Core/Internal/Http2/Http2StreamContext.cs index 72ccd8d7c0..eea80103a7 100644 --- a/src/Kestrel.Core/Internal/Http2/Http2StreamContext.cs +++ b/src/Kestrel.Core/Internal/Http2/Http2StreamContext.cs @@ -14,7 +14,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http2 public int StreamId { get; set; } public ServiceContext ServiceContext { get; set; } public IFeatureCollection ConnectionFeatures { get; set; } - public MemoryPool MemoryPool { get; set; } + public MemoryPool MemoryPool { get; set; } public IPEndPoint RemoteEndPoint { get; set; } public IPEndPoint LocalEndPoint { get; set; } public IHttp2StreamLifetimeHandler StreamLifetimeHandler { get; set; } diff --git a/src/Kestrel.Core/Internal/HttpConnection.cs b/src/Kestrel.Core/Internal/HttpConnection.cs index 471d8194a2..b3895a72c9 100644 --- a/src/Kestrel.Core/Internal/HttpConnection.cs +++ b/src/Kestrel.Core/Internal/HttpConnection.cs @@ -68,7 +68,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal public IPEndPoint LocalEndPoint => _context.LocalEndPoint; public IPEndPoint RemoteEndPoint => _context.RemoteEndPoint; - private MemoryPool MemoryPool => _context.MemoryPool; + private MemoryPool MemoryPool => _context.MemoryPool; // Internal for testing internal PipeOptions AdaptedInputPipeOptions => new PipeOptions diff --git a/src/Kestrel.Core/Internal/HttpConnectionContext.cs b/src/Kestrel.Core/Internal/HttpConnectionContext.cs index 445d07a644..b60d5702ac 100644 --- a/src/Kestrel.Core/Internal/HttpConnectionContext.cs +++ b/src/Kestrel.Core/Internal/HttpConnectionContext.cs @@ -18,7 +18,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal public ServiceContext ServiceContext { get; set; } public IFeatureCollection ConnectionFeatures { get; set; } public IList ConnectionAdapters { get; set; } - public MemoryPool MemoryPool { get; set; } + public MemoryPool MemoryPool { get; set; } public IPEndPoint LocalEndPoint { get; set; } public IPEndPoint RemoteEndPoint { get; set; } public IDuplexPipe Transport { get; set; } diff --git a/src/Kestrel.Core/Internal/Infrastructure/KestrelThreadPool.cs b/src/Kestrel.Core/Internal/Infrastructure/KestrelThreadPool.cs index 6fe8810a3f..b160a1df5b 100644 --- a/src/Kestrel.Core/Internal/Infrastructure/KestrelThreadPool.cs +++ b/src/Kestrel.Core/Internal/Infrastructure/KestrelThreadPool.cs @@ -2,6 +2,7 @@ // Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. using System; +using System.IO.Pipelines; using System.Threading; namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Infrastructure diff --git a/src/Kestrel.Core/Kestrel.Core.csproj b/src/Kestrel.Core/Kestrel.Core.csproj index 59b66b7373..68a0b5aaf5 100644 --- a/src/Kestrel.Core/Kestrel.Core.csproj +++ b/src/Kestrel.Core/Kestrel.Core.csproj @@ -21,6 +21,7 @@ + diff --git a/src/Kestrel.Transport.Abstractions/Internal/KestrelMemoryPool.cs b/src/Kestrel.Transport.Abstractions/Internal/KestrelMemoryPool.cs new file mode 100644 index 0000000000..61a065c6eb --- /dev/null +++ b/src/Kestrel.Transport.Abstractions/Internal/KestrelMemoryPool.cs @@ -0,0 +1,12 @@ +// 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.Buffers; + +namespace Microsoft.AspNetCore.Server.Kestrel.Transport.Abstractions.Internal +{ + public static class KestrelMemoryPool + { + public static MemoryPool Create() => new SlabMemoryPool(); + } +} diff --git a/src/Kestrel.Transport.Abstractions/Internal/TransportConnection.Features.cs b/src/Kestrel.Transport.Abstractions/Internal/TransportConnection.Features.cs index acd9e0dab1..4259da0128 100644 --- a/src/Kestrel.Transport.Abstractions/Internal/TransportConnection.Features.cs +++ b/src/Kestrel.Transport.Abstractions/Internal/TransportConnection.Features.cs @@ -95,7 +95,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Transport.Abstractions.Internal set => LocalPort = value; } - MemoryPool IConnectionTransportFeature.MemoryPool => MemoryPool; + MemoryPool IConnectionTransportFeature.MemoryPool => MemoryPool; IDuplexPipe IConnectionTransportFeature.Transport { diff --git a/src/Kestrel.Transport.Abstractions/Internal/TransportConnection.cs b/src/Kestrel.Transport.Abstractions/Internal/TransportConnection.cs index 34cb670e48..3aae48b16c 100644 --- a/src/Kestrel.Transport.Abstractions/Internal/TransportConnection.cs +++ b/src/Kestrel.Transport.Abstractions/Internal/TransportConnection.cs @@ -21,7 +21,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Transport.Abstractions.Internal public string ConnectionId { get; set; } - public virtual MemoryPool MemoryPool { get; } + public virtual MemoryPool MemoryPool { get; } public virtual PipeScheduler InputWriterScheduler { get; } public virtual PipeScheduler OutputReaderScheduler { get; } diff --git a/src/Kestrel.Transport.Abstractions/Kestrel.Transport.Abstractions.csproj b/src/Kestrel.Transport.Abstractions/Kestrel.Transport.Abstractions.csproj index cfbd7353cf..b8877c964a 100644 --- a/src/Kestrel.Transport.Abstractions/Kestrel.Transport.Abstractions.csproj +++ b/src/Kestrel.Transport.Abstractions/Kestrel.Transport.Abstractions.csproj @@ -15,5 +15,8 @@ + + + diff --git a/src/Kestrel.Transport.Libuv/Internal/LibuvConnection.cs b/src/Kestrel.Transport.Libuv/Internal/LibuvConnection.cs index 8471d89d61..bb98d1c8c1 100644 --- a/src/Kestrel.Transport.Libuv/Internal/LibuvConnection.cs +++ b/src/Kestrel.Transport.Libuv/Internal/LibuvConnection.cs @@ -122,7 +122,6 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Transport.Libuv.Internal { // EAGAIN/EWOULDBLOCK so just return the buffer. // http://docs.libuv.org/en/v1.x/stream.html#c.uv_read_cb - Input.Commit(); } else if (status > 0) { @@ -141,7 +140,6 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Transport.Libuv.Internal else { // Given a negative status, it's possible that OnAlloc wasn't called. - Input.Commit(); _socket.ReadStop(); IOException error = null; @@ -175,7 +173,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Transport.Libuv.Internal _bufferHandle.Dispose(); } - private async Task ApplyBackpressureAsync(ValueAwaiter flushTask) + private async Task ApplyBackpressureAsync(PipeAwaiter flushTask) { Log.ConnectionPause(ConnectionId); _socket.ReadStop(); @@ -183,7 +181,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Transport.Libuv.Internal var result = await flushTask; // If the reader isn't complete or cancelled then resume reading - if (!result.IsCompleted && !result.IsCancelled) + if (!result.IsCompleted && !result.IsCanceled) { Log.ConnectionResume(ConnectionId); StartReading(); diff --git a/src/Kestrel.Transport.Libuv/Internal/LibuvConnectionContext.cs b/src/Kestrel.Transport.Libuv/Internal/LibuvConnectionContext.cs index 39f68be604..1f2abb9f37 100644 --- a/src/Kestrel.Transport.Libuv/Internal/LibuvConnectionContext.cs +++ b/src/Kestrel.Transport.Libuv/Internal/LibuvConnectionContext.cs @@ -2,6 +2,7 @@ // Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. using System.Buffers; +using System.IO.Pipelines; using System.Threading; using Microsoft.AspNetCore.Server.Kestrel.Transport.Abstractions.Internal; @@ -16,7 +17,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Transport.Libuv.Internal public ListenerContext ListenerContext { get; set; } - public override MemoryPool MemoryPool => ListenerContext.Thread.MemoryPool; + public override MemoryPool MemoryPool => ListenerContext.Thread.MemoryPool; public override PipeScheduler InputWriterScheduler => ListenerContext.Thread; public override PipeScheduler OutputReaderScheduler => ListenerContext.Thread; } diff --git a/src/Kestrel.Transport.Libuv/Internal/LibuvOutputConsumer.cs b/src/Kestrel.Transport.Libuv/Internal/LibuvOutputConsumer.cs index 298c333d05..cb60e9a2e0 100644 --- a/src/Kestrel.Transport.Libuv/Internal/LibuvOutputConsumer.cs +++ b/src/Kestrel.Transport.Libuv/Internal/LibuvOutputConsumer.cs @@ -55,7 +55,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Transport.Libuv.Internal try { - if (result.IsCancelled) + if (result.IsCanceled) { break; } diff --git a/src/Kestrel.Transport.Libuv/Internal/LibuvThread.cs b/src/Kestrel.Transport.Libuv/Internal/LibuvThread.cs index 87774d835a..0d6530303f 100644 --- a/src/Kestrel.Transport.Libuv/Internal/LibuvThread.cs +++ b/src/Kestrel.Transport.Libuv/Internal/LibuvThread.cs @@ -5,10 +5,12 @@ using System; using System.Buffers; using System.Collections.Generic; using System.Diagnostics; +using System.IO.Pipelines; using System.Runtime.ExceptionServices; using System.Threading; using System.Threading.Tasks; using Microsoft.AspNetCore.Hosting; +using Microsoft.AspNetCore.Server.Kestrel.Transport.Abstractions.Internal; using Microsoft.AspNetCore.Server.Kestrel.Transport.Libuv.Internal.Networking; using Microsoft.Extensions.Logging; @@ -55,7 +57,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Transport.Libuv.Internal #endif QueueCloseHandle = PostCloseHandle; QueueCloseAsyncHandle = EnqueueCloseHandle; - MemoryPool = new MemoryPool(); + MemoryPool = KestrelMemoryPool.Create(); WriteReqPool = new WriteReqPool(this, _log); } @@ -68,7 +70,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Transport.Libuv.Internal public UvLoopHandle Loop { get { return _loop; } } - public MemoryPool MemoryPool { get; } + public MemoryPool MemoryPool { get; } public WriteReqPool WriteReqPool { get; } diff --git a/src/Kestrel.Transport.Libuv/Internal/Networking/UvWriteReq.cs b/src/Kestrel.Transport.Libuv/Internal/Networking/UvWriteReq.cs index 27d78bae3d..9a635ba5cc 100644 --- a/src/Kestrel.Transport.Libuv/Internal/Networking/UvWriteReq.cs +++ b/src/Kestrel.Transport.Libuv/Internal/Networking/UvWriteReq.cs @@ -48,7 +48,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Transport.Libuv.Internal.Networkin _bufs = handle + requestSize; } - public LibuvAwaitable WriteAsync(UvStreamHandle handle, ReadOnlyBuffer buffer) + public LibuvAwaitable WriteAsync(UvStreamHandle handle, ReadOnlySequence buffer) { Write(handle, buffer, LibuvAwaitable.Callback, _awaitable); return _awaitable; @@ -62,7 +62,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Transport.Libuv.Internal.Networkin private unsafe void Write( UvStreamHandle handle, - ReadOnlyBuffer buffer, + ReadOnlySequence buffer, Action callback, object state) { diff --git a/src/Kestrel.Transport.Sockets/Internal/SocketConnection.cs b/src/Kestrel.Transport.Sockets/Internal/SocketConnection.cs index 227101d1f9..87fee3379a 100644 --- a/src/Kestrel.Transport.Sockets/Internal/SocketConnection.cs +++ b/src/Kestrel.Transport.Sockets/Internal/SocketConnection.cs @@ -5,6 +5,7 @@ using System; using System.Buffers; using System.Diagnostics; using System.IO; +using System.IO.Pipelines; using System.Net; using System.Net.Sockets; using System.Threading.Tasks; @@ -26,7 +27,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Transport.Sockets.Internal private volatile bool _aborted; - internal SocketConnection(Socket socket, MemoryPool memoryPool, ISocketsTrace trace) + internal SocketConnection(Socket socket, MemoryPool memoryPool, ISocketsTrace trace) { Debug.Assert(socket != null); Debug.Assert(memoryPool != null); @@ -49,7 +50,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Transport.Sockets.Internal _sender = new SocketSender(_socket); } - public override MemoryPool MemoryPool { get; } + public override MemoryPool MemoryPool { get; } public override PipeScheduler InputWriterScheduler => PipeScheduler.Inline; public override PipeScheduler OutputReaderScheduler => PipeScheduler.ThreadPool; @@ -102,24 +103,18 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Transport.Sockets.Internal // Ensure we have some reasonable amount of buffer space var buffer = Input.GetMemory(MinAllocBufferSize); - try - { - var bytesReceived = await _receiver.ReceiveAsync(buffer); + var bytesReceived = await _receiver.ReceiveAsync(buffer); - if (bytesReceived == 0) - { - // FIN - _trace.ConnectionReadFin(ConnectionId); - break; - } - - Input.Advance(bytesReceived); - } - finally + if (bytesReceived == 0) { - Input.Commit(); + // FIN + _trace.ConnectionReadFin(ConnectionId); + break; } + Input.Advance(bytesReceived); + + var flushTask = Input.FlushAsync(); if (!flushTask.IsCompleted) @@ -197,7 +192,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Transport.Sockets.Internal var result = await Output.ReadAsync(); var buffer = result.Buffer; - if (result.IsCancelled) + if (result.IsCanceled) { break; } diff --git a/src/Kestrel.Transport.Sockets/Internal/SocketSender.cs b/src/Kestrel.Transport.Sockets/Internal/SocketSender.cs index ef88ce8731..2199c60ef5 100644 --- a/src/Kestrel.Transport.Sockets/Internal/SocketSender.cs +++ b/src/Kestrel.Transport.Sockets/Internal/SocketSender.cs @@ -25,7 +25,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Transport.Sockets.Internal _eventArgs.Completed += (_, e) => ((SocketAwaitable)e.UserToken).Complete(e.BytesTransferred, e.SocketError); } - public SocketAwaitable SendAsync(ReadOnlyBuffer buffers) + public SocketAwaitable SendAsync(ReadOnlySequence buffers) { if (buffers.IsSingleSegment) { @@ -74,7 +74,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Transport.Sockets.Internal return _awaitable; } - private List> GetBufferList(ReadOnlyBuffer buffer) + private List> GetBufferList(ReadOnlySequence buffer) { Debug.Assert(!buffer.IsEmpty); Debug.Assert(!buffer.IsSingleSegment); diff --git a/src/Kestrel.Transport.Sockets/SocketTransport.cs b/src/Kestrel.Transport.Sockets/SocketTransport.cs index cd729a5eb1..c27f24767a 100644 --- a/src/Kestrel.Transport.Sockets/SocketTransport.cs +++ b/src/Kestrel.Transport.Sockets/SocketTransport.cs @@ -19,7 +19,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Transport.Sockets { internal sealed class SocketTransport : ITransport { - private readonly MemoryPool _memoryPool = new MemoryPool(); + private readonly MemoryPool _memoryPool = KestrelMemoryPool.Create(); private readonly IEndPointInformation _endPointInformation; private readonly IConnectionHandler _handler; private readonly IApplicationLifetime _appLifetime; diff --git a/src/Protocols.Abstractions/DuplexPipe.cs b/src/Protocols.Abstractions/DuplexPipe.cs index 4e26d458ec..60fc029aec 100644 --- a/src/Protocols.Abstractions/DuplexPipe.cs +++ b/src/Protocols.Abstractions/DuplexPipe.cs @@ -18,7 +18,7 @@ namespace System.IO.Pipelines { } - public static DuplexPipePair CreateConnectionPair(MemoryPool memoryPool) + public static DuplexPipePair CreateConnectionPair(MemoryPool memoryPool) { return CreateConnectionPair(new PipeOptions(memoryPool), new PipeOptions(memoryPool)); } diff --git a/src/Protocols.Abstractions/Features/IConnectionTransportFeature.cs b/src/Protocols.Abstractions/Features/IConnectionTransportFeature.cs index 54e21226e4..1a5500b692 100644 --- a/src/Protocols.Abstractions/Features/IConnectionTransportFeature.cs +++ b/src/Protocols.Abstractions/Features/IConnectionTransportFeature.cs @@ -6,7 +6,7 @@ namespace Microsoft.AspNetCore.Protocols.Features { public interface IConnectionTransportFeature { - MemoryPool MemoryPool { get; } + MemoryPool MemoryPool { get; } IDuplexPipe Transport { get; set; } diff --git a/src/Protocols.Abstractions/Protocols.Abstractions.csproj b/src/Protocols.Abstractions/Protocols.Abstractions.csproj index 192ee595fa..ac03b392b4 100644 --- a/src/Protocols.Abstractions/Protocols.Abstractions.csproj +++ b/src/Protocols.Abstractions/Protocols.Abstractions.csproj @@ -17,7 +17,6 @@ - diff --git a/test/Kestrel.Core.Tests/ConnectionHandlerTests.cs b/test/Kestrel.Core.Tests/ConnectionHandlerTests.cs index 13aabd72ad..ee1cffefdc 100644 --- a/test/Kestrel.Core.Tests/ConnectionHandlerTests.cs +++ b/test/Kestrel.Core.Tests/ConnectionHandlerTests.cs @@ -7,6 +7,8 @@ using System.Threading.Tasks; using Microsoft.AspNetCore.Http.Features; using Microsoft.AspNetCore.Protocols.Features; using Microsoft.AspNetCore.Server.Kestrel.Core.Internal; +using Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Infrastructure; +using Microsoft.AspNetCore.Server.Kestrel.Transport.Abstractions.Internal; using Microsoft.AspNetCore.Testing; using Xunit; @@ -51,7 +53,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Tests Set(this); } - public MemoryPool MemoryPool { get; } = new MemoryPool(); + public MemoryPool MemoryPool { get; } = KestrelMemoryPool.Create(); public IDuplexPipe Transport { get; set; } public IDuplexPipe Application { get; set; } diff --git a/test/Kestrel.Core.Tests/Http1ConnectionTests.cs b/test/Kestrel.Core.Tests/Http1ConnectionTests.cs index 41c933a429..7428e20ec2 100644 --- a/test/Kestrel.Core.Tests/Http1ConnectionTests.cs +++ b/test/Kestrel.Core.Tests/Http1ConnectionTests.cs @@ -17,6 +17,7 @@ using Microsoft.AspNetCore.Server.Kestrel.Core.Features; using Microsoft.AspNetCore.Server.Kestrel.Core.Internal; using Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http; using Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Infrastructure; +using Microsoft.AspNetCore.Server.Kestrel.Transport.Abstractions.Internal; using Microsoft.AspNetCore.Testing; using Microsoft.Extensions.Logging; using Microsoft.Extensions.Primitives; @@ -32,14 +33,14 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Tests private readonly TestHttp1Connection _http1Connection; private readonly ServiceContext _serviceContext; private readonly Http1ConnectionContext _http1ConnectionContext; - private readonly MemoryPool _pipelineFactory; + private readonly MemoryPool _pipelineFactory; private SequencePosition _consumed; private SequencePosition _examined; private Mock _timeoutControl; public Http1ConnectionTests() { - _pipelineFactory = new MemoryPool(); + _pipelineFactory = KestrelMemoryPool.Create(); var pair = DuplexPipe.CreateConnectionPair(_pipelineFactory); _transport = pair.Transport; @@ -803,7 +804,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Tests _http1Connection.NextMessageBody = mockMessageBody.Object; var requestProcessingTask = _http1Connection.ProcessRequestsAsync(httpApplication); - + var data = Encoding.ASCII.GetBytes("POST / HTTP/1.1\r\nHost:\r\nConnection: close\r\ncontent-length: 1\r\n\r\n"); await _application.Output.WriteAsync(data); await requestProcessingTask.TimeoutAfter(TestConstants.DefaultTimeout); diff --git a/test/Kestrel.Core.Tests/Http2ConnectionTests.cs b/test/Kestrel.Core.Tests/Http2ConnectionTests.cs index 5c5a0c2925..b8f47eebee 100644 --- a/test/Kestrel.Core.Tests/Http2ConnectionTests.cs +++ b/test/Kestrel.Core.Tests/Http2ConnectionTests.cs @@ -18,6 +18,7 @@ using Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http; using Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http2; using Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http2.HPack; using Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Infrastructure; +using Microsoft.AspNetCore.Server.Kestrel.Transport.Abstractions.Internal; using Microsoft.AspNetCore.Testing; using Xunit; @@ -93,7 +94,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Tests private static readonly byte[] _noData = new byte[0]; private static readonly byte[] _maxData = Encoding.ASCII.GetBytes(new string('a', Http2Frame.MinAllowedMaxFrameSize)); - private readonly MemoryPool _memoryPool = new MemoryPool(); + private readonly MemoryPool _memoryPool = KestrelMemoryPool.Create(); private readonly DuplexPipe.DuplexPipePair _pair; private readonly TestApplicationErrorLogger _logger; private readonly Http2ConnectionContext _connectionContext; diff --git a/test/Kestrel.Core.Tests/HttpConnectionTests.cs b/test/Kestrel.Core.Tests/HttpConnectionTests.cs index 4e2829e863..27b1c3c5b3 100644 --- a/test/Kestrel.Core.Tests/HttpConnectionTests.cs +++ b/test/Kestrel.Core.Tests/HttpConnectionTests.cs @@ -10,6 +10,7 @@ using Microsoft.AspNetCore.Http.Features; using Microsoft.AspNetCore.Server.Kestrel.Core.Adapter.Internal; using Microsoft.AspNetCore.Server.Kestrel.Core.Internal; using Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Infrastructure; +using Microsoft.AspNetCore.Server.Kestrel.Transport.Abstractions.Internal; using Microsoft.AspNetCore.Testing; using Moq; using Xunit; @@ -18,13 +19,13 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Tests { public class HttpConnectionTests : IDisposable { - private readonly MemoryPool _memoryPool; + private readonly MemoryPool _memoryPool; private readonly HttpConnectionContext _httpConnectionContext; private readonly HttpConnection _httpConnection; public HttpConnectionTests() { - _memoryPool = new MemoryPool(); + _memoryPool = KestrelMemoryPool.Create(); var pair = DuplexPipe.CreateConnectionPair(_memoryPool); _httpConnectionContext = new HttpConnectionContext diff --git a/test/Kestrel.Core.Tests/HttpParserTests.cs b/test/Kestrel.Core.Tests/HttpParserTests.cs index b436c16211..8baa695f96 100644 --- a/test/Kestrel.Core.Tests/HttpParserTests.cs +++ b/test/Kestrel.Core.Tests/HttpParserTests.cs @@ -34,7 +34,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Tests string expectedVersion) { var parser = CreateParser(Mock.Of()); - var buffer = new ReadOnlyBuffer(Encoding.ASCII.GetBytes(requestLine)); + var buffer = new ReadOnlySequence(Encoding.ASCII.GetBytes(requestLine)); var requestHandler = new RequestHandler(); Assert.True(parser.ParseRequestLine(requestHandler, buffer, out var consumed, out var examined)); @@ -53,7 +53,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Tests public void ParseRequestLineReturnsFalseWhenGivenIncompleteRequestLines(string requestLine) { var parser = CreateParser(Mock.Of()); - var buffer = new ReadOnlyBuffer(Encoding.ASCII.GetBytes(requestLine)); + var buffer = new ReadOnlySequence(Encoding.ASCII.GetBytes(requestLine)); var requestHandler = new RequestHandler(); Assert.False(parser.ParseRequestLine(requestHandler, buffer, out var consumed, out var examined)); @@ -64,7 +64,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Tests public void ParseRequestLineDoesNotConsumeIncompleteRequestLine(string requestLine) { var parser = CreateParser(Mock.Of()); - var buffer = new ReadOnlyBuffer(Encoding.ASCII.GetBytes(requestLine)); + var buffer = new ReadOnlySequence(Encoding.ASCII.GetBytes(requestLine)); var requestHandler = new RequestHandler(); Assert.False(parser.ParseRequestLine(requestHandler, buffer, out var consumed, out var examined)); @@ -83,7 +83,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Tests .Returns(true); var parser = CreateParser(mockTrace.Object); - var buffer = new ReadOnlyBuffer(Encoding.ASCII.GetBytes(requestLine)); + var buffer = new ReadOnlySequence(Encoding.ASCII.GetBytes(requestLine)); var requestHandler = new RequestHandler(); var exception = Assert.Throws(() => @@ -105,7 +105,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Tests .Returns(true); var parser = CreateParser(mockTrace.Object); - var buffer = new ReadOnlyBuffer(Encoding.ASCII.GetBytes(requestLine)); + var buffer = new ReadOnlySequence(Encoding.ASCII.GetBytes(requestLine)); var requestHandler = new RequestHandler(); var exception = Assert.Throws(() => @@ -127,7 +127,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Tests .Returns(true); var parser = CreateParser(mockTrace.Object); - var buffer = new ReadOnlyBuffer(Encoding.ASCII.GetBytes(requestLine)); + var buffer = new ReadOnlySequence(Encoding.ASCII.GetBytes(requestLine)); var requestHandler = new RequestHandler(); var exception = Assert.Throws(() => @@ -178,7 +178,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Tests { var parser = CreateParser(Mock.Of()); - var buffer = new ReadOnlyBuffer(Encoding.ASCII.GetBytes(rawHeaders)); + var buffer = new ReadOnlySequence(Encoding.ASCII.GetBytes(rawHeaders)); var requestHandler = new RequestHandler(); Assert.False(parser.ParseHeaders(requestHandler, buffer, out var consumed, out var examined, out var consumedBytes)); } @@ -203,7 +203,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Tests { var parser = CreateParser(Mock.Of()); - var buffer = new ReadOnlyBuffer(Encoding.ASCII.GetBytes(rawHeaders)); + var buffer = new ReadOnlySequence(Encoding.ASCII.GetBytes(rawHeaders)); var requestHandler = new RequestHandler(); parser.ParseHeaders(requestHandler, buffer, out var consumed, out var examined, out var consumedBytes); @@ -293,7 +293,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Tests var parser = CreateParser(Mock.Of()); const string headerLine = "Header: value\r\n\r"; - var buffer1 = new ReadOnlyBuffer(Encoding.ASCII.GetBytes(headerLine)); + var buffer1 = new ReadOnlySequence(Encoding.ASCII.GetBytes(headerLine)); var requestHandler = new RequestHandler(); Assert.False(parser.ParseHeaders(requestHandler, buffer1, out var consumed, out var examined, out var consumedBytes)); @@ -301,7 +301,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Tests Assert.Equal(buffer1.End, examined); Assert.Equal(headerLine.Length - 1, consumedBytes); - var buffer2 = new ReadOnlyBuffer(Encoding.ASCII.GetBytes("\r\n")); + var buffer2 = new ReadOnlySequence(Encoding.ASCII.GetBytes("\r\n")); Assert.True(parser.ParseHeaders(requestHandler, buffer2, out consumed, out examined, out consumedBytes)); Assert.True(buffer2.Slice(consumed).IsEmpty); @@ -319,7 +319,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Tests .Returns(true); var parser = CreateParser(mockTrace.Object); - var buffer = new ReadOnlyBuffer(Encoding.ASCII.GetBytes(rawHeaders)); + var buffer = new ReadOnlySequence(Encoding.ASCII.GetBytes(rawHeaders)); var requestHandler = new RequestHandler(); var exception = Assert.Throws(() => @@ -340,7 +340,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Tests var parser = CreateParser(mockTrace.Object); // Invalid request line - var buffer = new ReadOnlyBuffer(Encoding.ASCII.GetBytes("GET % HTTP/1.1\r\n")); + var buffer = new ReadOnlySequence(Encoding.ASCII.GetBytes("GET % HTTP/1.1\r\n")); var requestHandler = new RequestHandler(); var exception = Assert.Throws(() => @@ -350,7 +350,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Tests Assert.Equal(StatusCodes.Status400BadRequest, (exception as BadHttpRequestException).StatusCode); // Unrecognized HTTP version - buffer = new ReadOnlyBuffer(Encoding.ASCII.GetBytes("GET / HTTP/1.2\r\n")); + buffer = new ReadOnlySequence(Encoding.ASCII.GetBytes("GET / HTTP/1.2\r\n")); exception = Assert.Throws(() => parser.ParseRequestLine(requestHandler, buffer, out var consumed, out var examined)); @@ -359,7 +359,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Tests Assert.Equal(StatusCodes.Status505HttpVersionNotsupported, (exception as BadHttpRequestException).StatusCode); // Invalid request header - buffer = new ReadOnlyBuffer(Encoding.ASCII.GetBytes("Header: value\n\r\n")); + buffer = new ReadOnlySequence(Encoding.ASCII.GetBytes("Header: value\n\r\n")); exception = Assert.Throws(() => parser.ParseHeaders(requestHandler, buffer, out var consumed, out var examined, out var consumedBytes)); @@ -388,7 +388,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Tests string expectedHeaderValue) { var parser = CreateParser(Mock.Of()); - var buffer = new ReadOnlyBuffer(Encoding.ASCII.GetBytes($"{headerName}:{rawHeaderValue}\r\n")); + var buffer = new ReadOnlySequence(Encoding.ASCII.GetBytes($"{headerName}:{rawHeaderValue}\r\n")); var requestHandler = new RequestHandler(); parser.ParseHeaders(requestHandler, buffer, out var consumed, out var examined, out var consumedBytes); @@ -406,7 +406,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Tests Assert.True(expectedHeaderNames.Count() == expectedHeaderValues.Count(), $"{nameof(expectedHeaderNames)} and {nameof(expectedHeaderValues)} sizes must match"); var parser = CreateParser(Mock.Of()); - var buffer = new ReadOnlyBuffer(Encoding.ASCII.GetBytes(rawHeaders)); + var buffer = new ReadOnlySequence(Encoding.ASCII.GetBytes(rawHeaders)); var requestHandler = new RequestHandler(); parser.ParseHeaders(requestHandler, buffer, out var consumed, out var examined, out var consumedBytes); @@ -466,7 +466,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Tests } } - private static ReadOnlyBuffer CreateBuffer(params string[] inputs) + private static ReadOnlySequence CreateBuffer(params string[] inputs) { var buffers = new byte[inputs.Length][]; for (int i = 0; i < inputs.Length; i++) @@ -477,7 +477,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Tests } // Copied from https://github.com/dotnet/corefx/blob/master/src/System.Memory/tests/ReadOnlyBuffer/ReadOnlySequenceFactory.cs - private static ReadOnlyBuffer CreateBuffer(params byte[][] inputs) + private static ReadOnlySequence CreateBuffer(params byte[][] inputs) { if (inputs == null || inputs.Length == 0) { @@ -516,7 +516,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Tests i++; } while (i < inputs.Length); - return new ReadOnlyBuffer(first, 0, last, last.Memory.Length); + return new ReadOnlySequence(first, 0, last, last.Memory.Length); } // Copied from https://github.com/dotnet/corefx/blob/master/src/System.Memory/tests/ReadOnlyBuffer/BufferSegment.cs diff --git a/test/Kestrel.Core.Tests/HttpProtocolFeatureCollectionTests.cs b/test/Kestrel.Core.Tests/HttpProtocolFeatureCollectionTests.cs index 4167386340..846b441862 100644 --- a/test/Kestrel.Core.Tests/HttpProtocolFeatureCollectionTests.cs +++ b/test/Kestrel.Core.Tests/HttpProtocolFeatureCollectionTests.cs @@ -10,6 +10,7 @@ using Microsoft.AspNetCore.Server.Kestrel.Core.Features; using Microsoft.AspNetCore.Server.Kestrel.Core.Internal; using Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http; using Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Infrastructure; +using Microsoft.AspNetCore.Server.Kestrel.Transport.Abstractions.Internal; using Microsoft.AspNetCore.Testing; using Moq; using Xunit; @@ -23,14 +24,14 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Tests private readonly TestHttp1Connection _http1Connection; private readonly ServiceContext _serviceContext; private readonly Http1ConnectionContext _http1ConnectionContext; - private readonly MemoryPool _pipelineFactory; + private readonly MemoryPool _pipelineFactory; private Mock _timeoutControl; private readonly IFeatureCollection _collection; public HttpProtocolFeatureCollectionTests() { - _pipelineFactory = new MemoryPool(); + _pipelineFactory = KestrelMemoryPool.Create(); var pair = DuplexPipe.CreateConnectionPair(_pipelineFactory); _transport = pair.Transport; diff --git a/test/Kestrel.Core.Tests/HttpResponseHeadersTests.cs b/test/Kestrel.Core.Tests/HttpResponseHeadersTests.cs index 0df993ab89..8a602351ad 100644 --- a/test/Kestrel.Core.Tests/HttpResponseHeadersTests.cs +++ b/test/Kestrel.Core.Tests/HttpResponseHeadersTests.cs @@ -9,6 +9,7 @@ using System.IO.Pipelines; using Microsoft.AspNetCore.Http; using Microsoft.AspNetCore.Http.Features; using Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http; +using Microsoft.AspNetCore.Server.Kestrel.Transport.Abstractions.Internal; using Microsoft.AspNetCore.Testing; using Microsoft.Extensions.Primitives; using Xunit; @@ -20,7 +21,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Tests [Fact] public void InitialDictionaryIsEmpty() { - using (var memoryPool = new MemoryPool()) + using (var memoryPool = KestrelMemoryPool.Create()) { var pair = DuplexPipe.CreateConnectionPair(memoryPool); var http1ConnectionContext = new Http1ConnectionContext diff --git a/test/Kestrel.Core.Tests/OutputProducerTests.cs b/test/Kestrel.Core.Tests/OutputProducerTests.cs index 8c489537ec..b9e334b527 100644 --- a/test/Kestrel.Core.Tests/OutputProducerTests.cs +++ b/test/Kestrel.Core.Tests/OutputProducerTests.cs @@ -7,6 +7,7 @@ using System.IO.Pipelines; using System.Threading; using Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http; using Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Infrastructure; +using Microsoft.AspNetCore.Server.Kestrel.Transport.Abstractions.Internal; using Microsoft.AspNetCore.Testing; using Moq; using Xunit; @@ -15,11 +16,11 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Tests { public class OutputProducerTests : IDisposable { - private readonly MemoryPool _memoryPool; + private readonly MemoryPool _memoryPool; public OutputProducerTests() { - _memoryPool = new MemoryPool(); + _memoryPool = KestrelMemoryPool.Create(); } public void Dispose() diff --git a/test/Kestrel.Core.Tests/PipeOptionsTests.cs b/test/Kestrel.Core.Tests/PipeOptionsTests.cs index ff3ab4ce27..71a4df8d05 100644 --- a/test/Kestrel.Core.Tests/PipeOptionsTests.cs +++ b/test/Kestrel.Core.Tests/PipeOptionsTests.cs @@ -2,9 +2,11 @@ // Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. using System.Buffers; +using System.IO.Pipelines; using System.Threading; using Microsoft.AspNetCore.Server.Kestrel.Core.Internal; using Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Infrastructure; +using Microsoft.AspNetCore.Server.Kestrel.Transport.Abstractions.Internal; using Microsoft.AspNetCore.Testing; using Moq; using Xunit; @@ -24,7 +26,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Tests serviceContext.ThreadPool = new LoggingThreadPool(null); var mockScheduler = Mock.Of(); - var outputPipeOptions = ConnectionHandler.GetOutputPipeOptions(serviceContext, new MemoryPool(), readerScheduler: mockScheduler); + var outputPipeOptions = ConnectionHandler.GetOutputPipeOptions(serviceContext, KestrelMemoryPool.Create(), readerScheduler: mockScheduler); Assert.Equal(expectedMaximumSizeLow, outputPipeOptions.ResumeWriterThreshold); Assert.Equal(expectedMaximumSizeHigh, outputPipeOptions.PauseWriterThreshold); @@ -42,7 +44,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Tests serviceContext.ThreadPool = new LoggingThreadPool(null); var mockScheduler = Mock.Of(); - var inputPipeOptions = ConnectionHandler.GetInputPipeOptions(serviceContext, new MemoryPool(), writerScheduler: mockScheduler); + var inputPipeOptions = ConnectionHandler.GetInputPipeOptions(serviceContext, KestrelMemoryPool.Create(), writerScheduler: mockScheduler); Assert.Equal(expectedMaximumSizeLow, inputPipeOptions.ResumeWriterThreshold); Assert.Equal(expectedMaximumSizeHigh, inputPipeOptions.PauseWriterThreshold); diff --git a/test/Kestrel.Core.Tests/PipelineExtensionTests.cs b/test/Kestrel.Core.Tests/PipelineExtensionTests.cs index 286dff7cbe..ed534a564f 100644 --- a/test/Kestrel.Core.Tests/PipelineExtensionTests.cs +++ b/test/Kestrel.Core.Tests/PipelineExtensionTests.cs @@ -6,6 +6,7 @@ using System.Buffers; using System.IO.Pipelines; using System.Text; using Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http; +using Microsoft.AspNetCore.Server.Kestrel.Transport.Abstractions.Internal; using Xunit; namespace Microsoft.AspNetCore.Server.Kestrel.Core.Tests @@ -16,7 +17,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Tests private const int _ulongMaxValueLength = 20; private readonly Pipe _pipe; - private readonly MemoryPool _memoryPool = new MemoryPool(); + private readonly MemoryPool _memoryPool = KestrelMemoryPool.Create(); public PipelineExtensionTests() { @@ -35,8 +36,9 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Tests public void WritesNumericToAscii(ulong number) { var writerBuffer = _pipe.Writer; - var writer = OutputWriter.Create(writerBuffer); + var writer = new BufferWriter(writerBuffer); writer.WriteNumeric(number); + writer.Commit(); writerBuffer.FlushAsync().GetAwaiter().GetResult(); var reader = _pipe.Reader.ReadAsync().GetAwaiter().GetResult(); @@ -52,7 +54,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Tests public void WritesNumericAcrossSpanBoundaries(int gapSize) { var writerBuffer = _pipe.Writer; - var writer = OutputWriter.Create(writerBuffer); + var writer = new BufferWriter(writerBuffer); // almost fill up the first block var spacer = new byte[writer.Span.Length - gapSize]; writer.Write(spacer); @@ -60,7 +62,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Tests var bufferLength = writer.Span.Length; writer.WriteNumeric(ulong.MaxValue); Assert.NotEqual(bufferLength, writer.Span.Length); - + writer.Commit(); writerBuffer.FlushAsync().GetAwaiter().GetResult(); var reader = _pipe.Reader.ReadAsync().GetAwaiter().GetResult(); @@ -83,8 +85,9 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Tests public void EncodesAsAscii(string input, byte[] expected) { var writerBuffer = _pipe.Writer; - var writer = OutputWriter.Create(writerBuffer); + var writer = new BufferWriter(writerBuffer); writer.WriteAsciiNoValidation(input); + writer.Commit(); writerBuffer.FlushAsync().GetAwaiter().GetResult(); var reader = _pipe.Reader.ReadAsync().GetAwaiter().GetResult(); @@ -110,8 +113,9 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Tests // WriteAscii doesn't validate if characters are in the ASCII range // but it shouldn't produce more than one byte per character var writerBuffer = _pipe.Writer; - var writer = OutputWriter.Create(writerBuffer); + var writer = new BufferWriter(writerBuffer); writer.WriteAsciiNoValidation(input); + writer.Commit(); writerBuffer.FlushAsync().GetAwaiter().GetResult(); var reader = _pipe.Reader.ReadAsync().GetAwaiter().GetResult(); @@ -123,11 +127,12 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Tests { const byte maxAscii = 0x7f; var writerBuffer = _pipe.Writer; - var writer = OutputWriter.Create(writerBuffer); + var writer = new BufferWriter(writerBuffer); for (var i = 0; i < maxAscii; i++) { writer.WriteAsciiNoValidation(new string((char)i, 1)); } + writer.Commit(); writerBuffer.FlushAsync().GetAwaiter().GetResult(); var reader = _pipe.Reader.ReadAsync().GetAwaiter().GetResult(); @@ -152,7 +157,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Tests { var testString = new string(' ', stringLength); var writerBuffer = _pipe.Writer; - var writer = OutputWriter.Create(writerBuffer); + var writer = new BufferWriter(writerBuffer); // almost fill up the first block var spacer = new byte[writer.Span.Length - gapSize]; writer.Write(spacer); @@ -161,7 +166,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Tests var bufferLength = writer.Span.Length; writer.WriteAsciiNoValidation(testString); Assert.NotEqual(bufferLength, writer.Span.Length); - + writer.Commit(); writerBuffer.FlushAsync().GetAwaiter().GetResult(); var reader = _pipe.Reader.ReadAsync().GetAwaiter().GetResult(); diff --git a/test/Kestrel.Core.Tests/TestInput.cs b/test/Kestrel.Core.Tests/TestInput.cs index 601fcd133e..d042e5c5bc 100644 --- a/test/Kestrel.Core.Tests/TestInput.cs +++ b/test/Kestrel.Core.Tests/TestInput.cs @@ -5,9 +5,11 @@ using System; using System.Buffers; using System.IO.Pipelines; using System.Text; +using System.Threading.Tasks; using Microsoft.AspNetCore.Http.Features; using Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http; using Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Infrastructure; +using Microsoft.AspNetCore.Server.Kestrel.Transport.Abstractions.Internal; using Microsoft.AspNetCore.Testing; using Moq; @@ -15,11 +17,11 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Tests { class TestInput : IDisposable { - private MemoryPool _memoryPool; + private MemoryPool _memoryPool; public TestInput() { - _memoryPool = new MemoryPool(); + _memoryPool = KestrelMemoryPool.Create(); var pair = DuplexPipe.CreateConnectionPair(_memoryPool); Transport = pair.Transport; Application = pair.Application; @@ -49,7 +51,8 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Tests public void Add(string text) { var data = Encoding.ASCII.GetBytes(text); - Application.Output.WriteAsync(data).Wait(); + async Task Write() => await Application.Output.WriteAsync(data); + Write().Wait(); } public void Fin() diff --git a/test/Kestrel.Transport.Libuv.Tests/LibuvOutputConsumerTests.cs b/test/Kestrel.Transport.Libuv.Tests/LibuvOutputConsumerTests.cs index d674ae6e96..1e9fb7f361 100644 --- a/test/Kestrel.Transport.Libuv.Tests/LibuvOutputConsumerTests.cs +++ b/test/Kestrel.Transport.Libuv.Tests/LibuvOutputConsumerTests.cs @@ -11,6 +11,7 @@ using Microsoft.AspNetCore.Http.Features; 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.Transport.Abstractions.Internal; using Microsoft.AspNetCore.Server.Kestrel.Transport.Libuv.Internal; using Microsoft.AspNetCore.Server.Kestrel.Transport.Libuv.Internal.Networking; using Microsoft.AspNetCore.Server.Kestrel.Transport.Libuv.Tests.TestHelpers; @@ -22,7 +23,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Transport.Libuv.Tests { public class LibuvOutputConsumerTests : IDisposable { - private readonly MemoryPool _memoryPool; + private readonly MemoryPool _memoryPool; private readonly MockLibuv _mockLibuv; private readonly LibuvThread _libuvThread; @@ -38,7 +39,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Transport.Libuv.Tests public LibuvOutputConsumerTests() { - _memoryPool = new MemoryPool(); + _memoryPool = KestrelMemoryPool.Create(); _mockLibuv = new MockLibuv(); var libuvTransport = new LibuvTransport(_mockLibuv, new TestLibuvTransportContext(), new ListenOptions((ulong)0)); @@ -340,6 +341,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Transport.Libuv.Tests ( pool: _memoryPool, readerScheduler: _libuvThread, + writerScheduler: PipeScheduler.Inline, pauseWriterThreshold: maxResponseBufferSize, resumeWriterThreshold: maxResponseBufferSize ); diff --git a/test/Kestrel.Transport.Libuv.Tests/MultipleLoopTests.cs b/test/Kestrel.Transport.Libuv.Tests/MultipleLoopTests.cs index b4dd9c64ae..463165ef88 100644 --- a/test/Kestrel.Transport.Libuv.Tests/MultipleLoopTests.cs +++ b/test/Kestrel.Transport.Libuv.Tests/MultipleLoopTests.cs @@ -67,7 +67,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Transport.Libuv.Tests await writeRequest.WriteAsync( serverConnectionPipe, - new ReadOnlyBuffer(new byte[] { 1, 2, 3, 4 })); + new ReadOnlySequence(new byte[] { 1, 2, 3, 4 })); writeRequest.Dispose(); serverConnectionPipe.Dispose(); diff --git a/test/Kestrel.Transport.Libuv.Tests/NetworkingTests.cs b/test/Kestrel.Transport.Libuv.Tests/NetworkingTests.cs index 24a1bf36a7..0e698d5477 100644 --- a/test/Kestrel.Transport.Libuv.Tests/NetworkingTests.cs +++ b/test/Kestrel.Transport.Libuv.Tests/NetworkingTests.cs @@ -161,7 +161,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Transport.Libuv.Tests { var req = new UvWriteReq(_logger); req.DangerousInit(loop); - var block = new ReadOnlyBuffer(new byte[] { 65, 66, 67, 68, 69 }); + var block = new ReadOnlySequence(new byte[] { 65, 66, 67, 68, 69 }); await req.WriteAsync( tcp2, diff --git a/test/Kestrel.Transport.Libuv.Tests/TestHelpers/MockConnectionHandler.cs b/test/Kestrel.Transport.Libuv.Tests/TestHelpers/MockConnectionHandler.cs index 2b2b380755..d248b4cfc2 100644 --- a/test/Kestrel.Transport.Libuv.Tests/TestHelpers/MockConnectionHandler.cs +++ b/test/Kestrel.Transport.Libuv.Tests/TestHelpers/MockConnectionHandler.cs @@ -13,8 +13,8 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Transport.Libuv.Tests.TestHelpers { public class MockConnectionHandler : IConnectionHandler { - public Func InputOptions { get; set; } = pool => new PipeOptions(pool); - public Func OutputOptions { get; set; } = pool => new PipeOptions(pool); + public Func, PipeOptions> InputOptions { get; set; } = pool => new PipeOptions(pool); + public Func, PipeOptions> OutputOptions { get; set; } = pool => new PipeOptions(pool); public void OnConnection(IFeatureCollection features) { diff --git a/tools/CodeGenerator/KnownHeaders.cs b/tools/CodeGenerator/KnownHeaders.cs index 7578113ba3..18a0613469 100644 --- a/tools/CodeGenerator/KnownHeaders.cs +++ b/tools/CodeGenerator/KnownHeaders.cs @@ -523,7 +523,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http return true; }} {(loop.ClassName == "HttpResponseHeaders" ? $@" - protected void CopyToFast(ref OutputWriter output) + internal void CopyToFast(ref BufferWriter output) {{ var tempBits = _bits | (_contentLength.HasValue ? {1L << 63}L : 0); {Each(loop.Headers.Where(header => header.Identifier != "ContentLength").OrderBy(h => !h.PrimaryHeader), header => $@"