From 8923b0da702515c50f79813b60f1595ae1c7d238 Mon Sep 17 00:00:00 2001 From: Stephen Halter Date: Mon, 20 Mar 2017 15:43:55 -0700 Subject: [PATCH] Use correct config for response buffer limit (#1516) --- .../Internal/Http/ListenerContext.cs | 2 +- .../FrameParsingOverheadBenchmark.cs | 2 +- .../RequestParsingBenchmark.cs | 2 +- .../ResponseHeaderCollectionBenchmark.cs | 2 +- .../ListenerContextTests.cs | 72 +++++++++++ .../SocketOutputTests.cs | 121 +++++++----------- test/shared/MockConnection.cs | 4 +- 7 files changed, 127 insertions(+), 78 deletions(-) create mode 100644 test/Microsoft.AspNetCore.Server.KestrelTests/ListenerContextTests.cs diff --git a/src/Microsoft.AspNetCore.Server.Kestrel/Internal/Http/ListenerContext.cs b/src/Microsoft.AspNetCore.Server.Kestrel/Internal/Http/ListenerContext.cs index 721a31bb60..4a94842ac3 100644 --- a/src/Microsoft.AspNetCore.Server.Kestrel/Internal/Http/ListenerContext.cs +++ b/src/Microsoft.AspNetCore.Server.Kestrel/Internal/Http/ListenerContext.cs @@ -68,7 +68,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Internal.Http private long GetOutputResponseBufferSize() { - var bufferSize = ServiceContext.ServerOptions.Limits.MaxRequestBufferSize; + var bufferSize = ServiceContext.ServerOptions.Limits.MaxResponseBufferSize; if (bufferSize == 0) { // 0 = no buffering so we need to configure the pipe so the the writer waits on the reader directly diff --git a/test/Microsoft.AspNetCore.Server.Kestrel.Performance/FrameParsingOverheadBenchmark.cs b/test/Microsoft.AspNetCore.Server.Kestrel.Performance/FrameParsingOverheadBenchmark.cs index 40bf9a6cd0..a772eee781 100644 --- a/test/Microsoft.AspNetCore.Server.Kestrel.Performance/FrameParsingOverheadBenchmark.cs +++ b/test/Microsoft.AspNetCore.Server.Kestrel.Performance/FrameParsingOverheadBenchmark.cs @@ -21,7 +21,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Performance [Setup] public void Setup() { - var connectionContext = new MockConnection(new KestrelServerOptions()); + var connectionContext = new MockConnection(); connectionContext.ListenerContext.ServiceContext.HttpParserFactory = frame => NullParser.Instance; _frame = new Frame(application: null, context: connectionContext); diff --git a/test/Microsoft.AspNetCore.Server.Kestrel.Performance/RequestParsingBenchmark.cs b/test/Microsoft.AspNetCore.Server.Kestrel.Performance/RequestParsingBenchmark.cs index 0b2e0ee914..dc07d53270 100644 --- a/test/Microsoft.AspNetCore.Server.Kestrel.Performance/RequestParsingBenchmark.cs +++ b/test/Microsoft.AspNetCore.Server.Kestrel.Performance/RequestParsingBenchmark.cs @@ -21,7 +21,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Performance [Setup] public void Setup() { - var connectionContext = new MockConnection(new KestrelServerOptions()); + var connectionContext = new MockConnection(); connectionContext.ListenerContext.ServiceContext.HttpParserFactory = frame => new KestrelHttpParser(frame.ConnectionContext.ListenerContext.ServiceContext.Log); Frame = new Frame(application: null, context: connectionContext); diff --git a/test/Microsoft.AspNetCore.Server.Kestrel.Performance/ResponseHeaderCollectionBenchmark.cs b/test/Microsoft.AspNetCore.Server.Kestrel.Performance/ResponseHeaderCollectionBenchmark.cs index 691b0da43a..48b79138b5 100644 --- a/test/Microsoft.AspNetCore.Server.Kestrel.Performance/ResponseHeaderCollectionBenchmark.cs +++ b/test/Microsoft.AspNetCore.Server.Kestrel.Performance/ResponseHeaderCollectionBenchmark.cs @@ -167,7 +167,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Performance [Setup] public void Setup() { - var connectionContext = new MockConnection(new KestrelServerOptions()); + var connectionContext = new MockConnection(); connectionContext.ListenerContext.ServiceContext.HttpParserFactory = f => new KestrelHttpParser(f.ConnectionContext.ListenerContext.ServiceContext.Log); var frame = new Frame(application: null, context: connectionContext); frame.Reset(); diff --git a/test/Microsoft.AspNetCore.Server.KestrelTests/ListenerContextTests.cs b/test/Microsoft.AspNetCore.Server.KestrelTests/ListenerContextTests.cs new file mode 100644 index 0000000000..4004c95850 --- /dev/null +++ b/test/Microsoft.AspNetCore.Server.KestrelTests/ListenerContextTests.cs @@ -0,0 +1,72 @@ +// 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.IO.Pipelines; +using Microsoft.AspNetCore.Server.Kestrel.Internal; +using Microsoft.AspNetCore.Server.Kestrel.Internal.Http; +using Microsoft.AspNetCore.Server.Kestrel.Internal.Infrastructure; +using Microsoft.AspNetCore.Testing; +using Xunit; + +namespace Microsoft.AspNetCore.Server.KestrelTests +{ + public class ListenerContextTests + { + [Theory] + [InlineData(10, 10, 10)] + [InlineData(0, 1, 1)] + [InlineData(null, 0, 0)] + public void LibuvOutputPipeOptionsConfiguredCorrectly(long? maxResponseBufferSize, long expectedMaximumSizeLow, long expectedMaximumSizeHigh) + { + var serviceContext = new TestServiceContext(); + serviceContext.ServerOptions.Limits.MaxResponseBufferSize = maxResponseBufferSize; + serviceContext.ThreadPool = new LoggingThreadPool(null); + + var listenerContext = new ListenerContext(serviceContext) + { + Thread = new KestrelThread(new KestrelEngine(null, serviceContext)) + }; + + Assert.Equal(expectedMaximumSizeLow, listenerContext.LibuvOutputPipeOptions.MaximumSizeLow); + Assert.Equal(expectedMaximumSizeHigh, listenerContext.LibuvOutputPipeOptions.MaximumSizeHigh); + Assert.Same(listenerContext.Thread, listenerContext.LibuvOutputPipeOptions.ReaderScheduler); + Assert.Same(serviceContext.ThreadPool, listenerContext.LibuvOutputPipeOptions.WriterScheduler); + } + + [Theory] + [InlineData(10, 10, 10)] + [InlineData(null, 0, 0)] + public void LibuvInputPipeOptionsConfiguredCorrectly(long? maxRequestBufferSize, long expectedMaximumSizeLow, long expectedMaximumSizeHigh) + { + var serviceContext = new TestServiceContext(); + serviceContext.ServerOptions.Limits.MaxRequestBufferSize = maxRequestBufferSize; + serviceContext.ThreadPool = new LoggingThreadPool(null); + + var listenerContext = new ListenerContext(serviceContext) + { + Thread = new KestrelThread(new KestrelEngine(null, serviceContext)) + }; + + Assert.Equal(expectedMaximumSizeLow, listenerContext.LibuvInputPipeOptions.MaximumSizeLow); + Assert.Equal(expectedMaximumSizeHigh, listenerContext.LibuvInputPipeOptions.MaximumSizeHigh); + Assert.Same(serviceContext.ThreadPool, listenerContext.LibuvInputPipeOptions.ReaderScheduler); + Assert.Same(listenerContext.Thread, listenerContext.LibuvInputPipeOptions.WriterScheduler); + } + + [Theory] + [InlineData(10, 10, 10)] + [InlineData(null, 0, 0)] + public void AdaptedPipeOptionsConfiguredCorrectly(long? maxRequestBufferSize, long expectedMaximumSizeLow, long expectedMaximumSizeHigh) + { + var serviceContext = new TestServiceContext(); + serviceContext.ServerOptions.Limits.MaxRequestBufferSize = maxRequestBufferSize; + + var listenerContext = new ListenerContext(serviceContext); + + Assert.Equal(expectedMaximumSizeLow, listenerContext.AdaptedPipeOptions.MaximumSizeLow); + Assert.Equal(expectedMaximumSizeHigh, listenerContext.AdaptedPipeOptions.MaximumSizeHigh); + Assert.Same(InlineScheduler.Default, listenerContext.AdaptedPipeOptions.ReaderScheduler); + Assert.Same(InlineScheduler.Default, listenerContext.AdaptedPipeOptions.WriterScheduler); + } + } +} diff --git a/test/Microsoft.AspNetCore.Server.KestrelTests/SocketOutputTests.cs b/test/Microsoft.AspNetCore.Server.KestrelTests/SocketOutputTests.cs index ba88696ff7..544c9af39b 100644 --- a/test/Microsoft.AspNetCore.Server.KestrelTests/SocketOutputTests.cs +++ b/test/Microsoft.AspNetCore.Server.KestrelTests/SocketOutputTests.cs @@ -9,7 +9,6 @@ using System.Threading.Tasks; using Microsoft.AspNetCore.Server.Kestrel; using Microsoft.AspNetCore.Server.Kestrel.Internal; using Microsoft.AspNetCore.Server.Kestrel.Internal.Http; -using Microsoft.AspNetCore.Server.Kestrel.Internal.Infrastructure; using Microsoft.AspNetCore.Server.KestrelTests.TestHelpers; using Microsoft.AspNetCore.Testing; using Xunit; @@ -18,43 +17,19 @@ namespace Microsoft.AspNetCore.Server.KestrelTests { public class SocketOutputTests { - public static TheoryData MaxResponseBufferSizeData => new TheoryData + public static TheoryData MaxResponseBufferSizeData => new TheoryData { - new KestrelServerOptions(), - new KestrelServerOptions - { - Limits = { MaxResponseBufferSize = 0 } - }, - new KestrelServerOptions - { - Limits = { MaxResponseBufferSize = 1024 } - }, - new KestrelServerOptions - { - Limits = { MaxResponseBufferSize = 1024 * 1024 } - }, - new KestrelServerOptions - { - Limits = { MaxResponseBufferSize = null } - }, + new KestrelServerOptions().Limits.MaxResponseBufferSize, 0, 1024, 1024 * 1024, null }; - public static TheoryData PositiveMaxResponseBufferSizeData => new TheoryData + public static TheoryData PositiveMaxResponseBufferSizeData => new TheoryData { - new KestrelServerOptions(), - new KestrelServerOptions - { - Limits = { MaxResponseBufferSize = 1024 } - }, - new KestrelServerOptions - { - Limits = { MaxResponseBufferSize = (1024 * 1024) + 1 } - } + (int)new KestrelServerOptions().Limits.MaxResponseBufferSize, 1024, (1024 * 1024) + 1 }; [Theory] [MemberData(nameof(MaxResponseBufferSizeData))] - public async Task CanWrite1MB(KestrelServerOptions options) + public async Task CanWrite1MB(long? maxResponseBufferSize) { // This test was added because when initially implementing write-behind buffering in // SocketOutput, the write callback would never be invoked for writes larger than @@ -71,15 +46,17 @@ namespace Microsoft.AspNetCore.Server.KestrelTests var socket = new MockSocket(mockLibuv, kestrelThread.Loop.ThreadId, new TestKestrelTrace()); var trace = new KestrelTrace(new TestKestrelTrace()); - var connection = new MockConnection(options); + + // ListenerContext will set MaximumSizeHigh/Low to zero when MaxResponseBufferSize is null. + // This is verified in ListenerContextTests.LibuvOutputPipeOptionsConfiguredCorrectly. var pipeOptions = new PipeOptions { ReaderScheduler = kestrelThread, - MaximumSizeHigh = options.Limits.MaxResponseBufferSize ?? 0, - MaximumSizeLow = options.Limits.MaxResponseBufferSize ?? 0, + MaximumSizeHigh = maxResponseBufferSize ?? 0, + MaximumSizeLow = maxResponseBufferSize ?? 0, }; var pipe = factory.Create(pipeOptions); - var socketOutput = new SocketOutput(pipe, kestrelThread, socket, connection, "0", trace); + var socketOutput = new SocketOutput(pipe, kestrelThread, socket, new MockConnection(), "0", trace); // At least one run of this test should have a MaxResponseBufferSize < 1 MB. var bufferSize = 1024 * 1024; @@ -120,15 +97,17 @@ namespace Microsoft.AspNetCore.Server.KestrelTests var socket = new MockSocket(mockLibuv, kestrelThread.Loop.ThreadId, new TestKestrelTrace()); var trace = new KestrelTrace(new TestKestrelTrace()); - var options = new KestrelServerOptions { Limits = { MaxResponseBufferSize = null } }; + + // ListenerContext will set MaximumSizeHigh/Low to zero when MaxResponseBufferSize is null. + // This is verified in ListenerContextTests.LibuvOutputPipeOptionsConfiguredCorrectly. var pipeOptions = new PipeOptions { ReaderScheduler = kestrelThread, - MaximumSizeHigh = options.Limits.MaxResponseBufferSize ?? 0, - MaximumSizeLow = options.Limits.MaxResponseBufferSize ?? 0, + MaximumSizeHigh = 0, + MaximumSizeLow = 0, }; var pipe = factory.Create(pipeOptions); - var socketOutput = new SocketOutput(pipe, kestrelThread, socket, new MockConnection(options), "0", trace); + var socketOutput = new SocketOutput(pipe, kestrelThread, socket, new MockConnection(), "0", trace); // Don't want to allocate anything too huge for perf. This is at least larger than the default buffer. var bufferSize = 1024 * 1024; @@ -177,7 +156,9 @@ namespace Microsoft.AspNetCore.Server.KestrelTests var socket = new MockSocket(mockLibuv, kestrelThread.Loop.ThreadId, new TestKestrelTrace()); var trace = new KestrelTrace(new TestKestrelTrace()); - var options = new KestrelServerOptions { Limits = { MaxResponseBufferSize = 0 } }; + + // ListenerContext will set MaximumSizeHigh/Low to 1 when MaxResponseBufferSize is zero. + // This is verified in ListenerContextTests.LibuvOutputPipeOptionsConfiguredCorrectly. var pipeOptions = new PipeOptions { ReaderScheduler = kestrelThread, @@ -185,7 +166,7 @@ namespace Microsoft.AspNetCore.Server.KestrelTests MaximumSizeLow = 1, }; var pipe = factory.Create(pipeOptions); - var socketOutput = new SocketOutput(pipe, kestrelThread, socket, new MockConnection(options), "0", trace); + var socketOutput = new SocketOutput(pipe, kestrelThread, socket, new MockConnection(), "0", trace); var bufferSize = 1; var buffer = new ArraySegment(new byte[bufferSize], 0, bufferSize); @@ -221,9 +202,8 @@ namespace Microsoft.AspNetCore.Server.KestrelTests [Theory] [MemberData(nameof(PositiveMaxResponseBufferSizeData))] - public async Task WritesDontCompleteImmediatelyWhenTooManyBytesAreAlreadyBuffered(KestrelServerOptions options) + public async Task WritesDontCompleteImmediatelyWhenTooManyBytesAreAlreadyBuffered(int maxResponseBufferSize) { - var maxBytesPreCompleted = (int)options.Limits.MaxResponseBufferSize.Value; var completeQueue = new ConcurrentQueue>(); // Arrange @@ -245,17 +225,16 @@ namespace Microsoft.AspNetCore.Server.KestrelTests var socket = new MockSocket(mockLibuv, kestrelThread.Loop.ThreadId, new TestKestrelTrace()); var trace = new KestrelTrace(new TestKestrelTrace()); - var mockConnection = new MockConnection(options); var pipeOptions = new PipeOptions { ReaderScheduler = kestrelThread, - MaximumSizeHigh = options.Limits.MaxResponseBufferSize ?? 0, - MaximumSizeLow = options.Limits.MaxResponseBufferSize ?? 0, + MaximumSizeHigh = maxResponseBufferSize, + MaximumSizeLow = maxResponseBufferSize, }; var pipe = factory.Create(pipeOptions); - var socketOutput = new SocketOutput(pipe, kestrelThread, socket, mockConnection, "0", trace); + var socketOutput = new SocketOutput(pipe, kestrelThread, socket, new MockConnection(), "0", trace); - var bufferSize = maxBytesPreCompleted - 1; + var bufferSize = maxResponseBufferSize - 1; var buffer = new ArraySegment(new byte[bufferSize], 0, bufferSize); // Act @@ -295,9 +274,8 @@ namespace Microsoft.AspNetCore.Server.KestrelTests [Theory] [MemberData(nameof(PositiveMaxResponseBufferSizeData))] - public async Task WritesDontCompleteImmediatelyWhenTooManyBytesIncludingNonImmediateAreAlreadyBuffered(KestrelServerOptions options) + public async Task WritesDontCompleteImmediatelyWhenTooManyBytesIncludingNonImmediateAreAlreadyBuffered(int maxResponseBufferSize) { - var maxBytesPreCompleted = (int)options.Limits.MaxResponseBufferSize.Value; var completeQueue = new ConcurrentQueue>(); // Arrange @@ -319,17 +297,16 @@ namespace Microsoft.AspNetCore.Server.KestrelTests var socket = new MockSocket(mockLibuv, kestrelThread.Loop.ThreadId, new TestKestrelTrace()); var trace = new KestrelTrace(new TestKestrelTrace()); - var mockConnection = new MockConnection(options); var pipeOptions = new PipeOptions { ReaderScheduler = kestrelThread, - MaximumSizeHigh = options.Limits.MaxResponseBufferSize ?? 0, - MaximumSizeLow = options.Limits.MaxResponseBufferSize ?? 0, + MaximumSizeHigh = maxResponseBufferSize, + MaximumSizeLow = maxResponseBufferSize, }; var pipe = factory.Create(pipeOptions); - var socketOutput = new SocketOutput(pipe, kestrelThread, socket, mockConnection, "0", trace); + var socketOutput = new SocketOutput(pipe, kestrelThread, socket, new MockConnection(), "0", trace); - var bufferSize = maxBytesPreCompleted / 2; + var bufferSize = maxResponseBufferSize / 2; var data = new byte[bufferSize]; var halfWriteBehindBuffer = new ArraySegment(data, 0, bufferSize); @@ -374,9 +351,8 @@ namespace Microsoft.AspNetCore.Server.KestrelTests [Theory] [MemberData(nameof(PositiveMaxResponseBufferSizeData))] - public async Task FailedWriteCompletesOrCancelsAllPendingTasks(KestrelServerOptions options) + public async Task FailedWriteCompletesOrCancelsAllPendingTasks(int maxResponseBufferSize) { - var maxBytesPreCompleted = (int)options.Limits.MaxResponseBufferSize.Value; var completeQueue = new ConcurrentQueue>(); // Arrange @@ -399,19 +375,19 @@ namespace Microsoft.AspNetCore.Server.KestrelTests var socket = new MockSocket(mockLibuv, kestrelThread.Loop.ThreadId, new TestKestrelTrace()); var trace = new KestrelTrace(new TestKestrelTrace()); - using (var mockConnection = new MockConnection(options)) + using (var mockConnection = new MockConnection()) { var pipeOptions = new PipeOptions { ReaderScheduler = kestrelThread, - MaximumSizeHigh = options.Limits.MaxResponseBufferSize ?? 0, - MaximumSizeLow = options.Limits.MaxResponseBufferSize ?? 0, + MaximumSizeHigh = maxResponseBufferSize, + MaximumSizeLow = maxResponseBufferSize, }; var pipe = factory.Create(pipeOptions); var abortedSource = mockConnection.RequestAbortedSource; var socketOutput = new SocketOutput(pipe, kestrelThread, socket, mockConnection, "0", trace); - var bufferSize = maxBytesPreCompleted - 1; + var bufferSize = maxResponseBufferSize - 1; var data = new byte[bufferSize]; var fullBuffer = new ArraySegment(data, 0, bufferSize); @@ -467,9 +443,8 @@ namespace Microsoft.AspNetCore.Server.KestrelTests [Theory] [MemberData(nameof(PositiveMaxResponseBufferSizeData))] - public async Task WritesDontGetCompletedTooQuickly(KestrelServerOptions options) + public async Task WritesDontGetCompletedTooQuickly(int maxResponseBufferSize) { - var maxBytesPreCompleted = (int)options.Limits.MaxResponseBufferSize.Value; var completeQueue = new ConcurrentQueue>(); // Arrange @@ -491,17 +466,16 @@ namespace Microsoft.AspNetCore.Server.KestrelTests var socket = new MockSocket(mockLibuv, kestrelThread.Loop.ThreadId, new TestKestrelTrace()); var trace = new KestrelTrace(new TestKestrelTrace()); - var mockConnection = new MockConnection(options); var pipeOptions = new PipeOptions { ReaderScheduler = kestrelThread, - MaximumSizeHigh = options.Limits.MaxResponseBufferSize ?? 0, - MaximumSizeLow = options.Limits.MaxResponseBufferSize ?? 0, + MaximumSizeHigh = maxResponseBufferSize, + MaximumSizeLow = maxResponseBufferSize, }; var pipe = factory.Create(pipeOptions); - var socketOutput = new SocketOutput(pipe, kestrelThread, socket, mockConnection, "0", trace); + var socketOutput = new SocketOutput(pipe, kestrelThread, socket, new MockConnection(), "0", trace); - var bufferSize = maxBytesPreCompleted - 1; + var bufferSize = maxResponseBufferSize - 1; var buffer = new ArraySegment(new byte[bufferSize], 0, bufferSize); // Act (Pre-complete the maximum number of bytes in preparation for the rest of the test) @@ -538,7 +512,7 @@ namespace Microsoft.AspNetCore.Server.KestrelTests [Theory] [MemberData(nameof(MaxResponseBufferSizeData))] - public async Task WritesAreAggregated(KestrelServerOptions options) + public async Task WritesAreAggregated(long? maxResponseBufferSize) { var writeCalled = false; var writeCount = 0; @@ -563,14 +537,17 @@ namespace Microsoft.AspNetCore.Server.KestrelTests var socket = new MockSocket(mockLibuv, kestrelThread.Loop.ThreadId, new TestKestrelTrace()); var trace = new KestrelTrace(new TestKestrelTrace()); + + // ListenerContext will set MaximumSizeHigh/Low to zero when MaxResponseBufferSize is null. + // This is verified in ListenerContextTests.LibuvOutputPipeOptionsConfiguredCorrectly. var pipeOptions = new PipeOptions { ReaderScheduler = kestrelThread, - MaximumSizeHigh = options.Limits.MaxResponseBufferSize ?? 0, - MaximumSizeLow = options.Limits.MaxResponseBufferSize ?? 0, + MaximumSizeHigh = maxResponseBufferSize ?? 0, + MaximumSizeLow = maxResponseBufferSize ?? 0, }; var pipe = factory.Create(pipeOptions); - var socketOutput = new SocketOutput(pipe, kestrelThread, socket, new MockConnection(new KestrelServerOptions()), "0", trace); + var socketOutput = new SocketOutput(pipe, kestrelThread, socket, new MockConnection(), "0", trace); mockLibuv.KestrelThreadBlocker.Reset(); @@ -616,7 +593,7 @@ namespace Microsoft.AspNetCore.Server.KestrelTests var socket = new MockSocket(mockLibuv, kestrelThread.Loop.ThreadId, new TestKestrelTrace()); var trace = new KestrelTrace(new TestKestrelTrace()); - var connection = new MockConnection(new KestrelServerOptions()); + var connection = new MockConnection(); var pipeOptions = new PipeOptions { ReaderScheduler = kestrelThread, diff --git a/test/shared/MockConnection.cs b/test/shared/MockConnection.cs index c68870cde6..56a9e84d56 100644 --- a/test/shared/MockConnection.cs +++ b/test/shared/MockConnection.cs @@ -16,11 +16,11 @@ namespace Microsoft.AspNetCore.Testing { private readonly TaskCompletionSource _socketClosedTcs = new TaskCompletionSource(); - public MockConnection(KestrelServerOptions options) + public MockConnection() { ConnectionControl = this; RequestAbortedSource = new CancellationTokenSource(); - ListenerContext = new ListenerContext(new ServiceContext {ServerOptions = options}) + ListenerContext = new ListenerContext(new ServiceContext()) { ListenOptions = new ListenOptions(new IPEndPoint(IPAddress.Loopback, 5000)) };