From 2f2a0047a1715db6d3b82f4c74288f63aaf1fdd7 Mon Sep 17 00:00:00 2001 From: Pavel Krymets Date: Mon, 30 Apr 2018 08:37:15 -0700 Subject: [PATCH] React to pool cleanup (#2526) --- Directory.Build.props | 4 + .../ResponseHeadersWritingBenchmark.cs | 12 +- build/dependencies.props | 2 +- src/Directory.Build.props | 3 - .../Adapter/Internal/LoggingStream.cs | 5 + .../Internal/KestrelMemoryPool.cs | 15 +- src/Kestrel.Transport.Libuv/AssemblyInfo.cs | 6 + .../Internal/LibuvThread.cs | 22 ++- .../LibuvTransportOptions.cs | 4 + src/Kestrel.Transport.Sockets/AssemblyInfo.cs | 6 + .../SocketTransport.cs | 6 +- .../SocketTransportFactory.cs | 2 +- .../SocketTransportOptions.cs | 4 + .../Http2ConnectionTests.cs | 6 +- .../PipelineExtensionTests.cs | 2 + test/Kestrel.Core.Tests/TestInput.cs | 4 + .../ChunkedRequestTests.cs | 7 +- .../DiagnosticMemoryPoolFactory.cs | 46 +++++ .../MaxRequestBufferSizeTests.cs | 20 +- test/Kestrel.FunctionalTests/RequestTests.cs | 10 +- test/Kestrel.FunctionalTests/ResponseTests.cs | 21 +- .../TransportSelector.cs | 6 +- .../LibuvConnectionTests.cs | 21 +- .../LibuvTransportTests.cs | 3 + .../TransportSelector.cs | 6 +- test/shared/CompositeKestrelTrace.cs | 187 ++++++++++++++++++ test/shared/TestApplicationErrorLogger.cs | 5 +- test/shared/TestServiceContext.cs | 12 +- test/xunit.runner.json | 3 +- 29 files changed, 400 insertions(+), 50 deletions(-) create mode 100644 src/Kestrel.Transport.Libuv/AssemblyInfo.cs create mode 100644 src/Kestrel.Transport.Sockets/AssemblyInfo.cs create mode 100644 test/Kestrel.FunctionalTests/DiagnosticMemoryPoolFactory.cs create mode 100644 test/shared/CompositeKestrelTrace.cs diff --git a/Directory.Build.props b/Directory.Build.props index 170752b08d..7aaac5fb80 100644 --- a/Directory.Build.props +++ b/Directory.Build.props @@ -7,6 +7,10 @@ + + $(DefineConstants);INNER_LOOP + + Microsoft ASP.NET Core https://github.com/aspnet/KestrelHttpServer diff --git a/benchmarks/Kestrel.Performance/ResponseHeadersWritingBenchmark.cs b/benchmarks/Kestrel.Performance/ResponseHeadersWritingBenchmark.cs index 69df7fdb88..2134a7d5b4 100644 --- a/benchmarks/Kestrel.Performance/ResponseHeadersWritingBenchmark.cs +++ b/benchmarks/Kestrel.Performance/ResponseHeadersWritingBenchmark.cs @@ -26,6 +26,8 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Performance private MemoryPool _memoryPool; + private DuplexPipe.DuplexPipePair _pair; + [Params( BenchmarkTypes.TechEmpowerPlaintext, BenchmarkTypes.PlaintextChunked, @@ -115,7 +117,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Performance { _memoryPool = KestrelMemoryPool.Create(); var options = new PipeOptions(_memoryPool, readerScheduler: PipeScheduler.Inline, writerScheduler: PipeScheduler.Inline, useSynchronizationContext: false); - var pair = DuplexPipe.CreateConnectionPair(options, options); + _pair = DuplexPipe.CreateConnectionPair(options, options); var serviceContext = new ServiceContext { @@ -131,8 +133,8 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Performance ConnectionFeatures = new FeatureCollection(), MemoryPool = _memoryPool, TimeoutControl = new MockTimeoutControl(), - Application = pair.Application, - Transport = pair.Transport + Application = _pair.Application, + Transport = _pair.Transport }); http1Connection.Reset(); @@ -143,6 +145,10 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Performance [IterationCleanup] public void Cleanup() { + _pair.Application.Input.Complete(); + _pair.Application.Output.Complete(); + _pair.Transport.Input.Complete(); + _pair.Transport.Output.Complete(); _memoryPool.Dispose(); } diff --git a/build/dependencies.props b/build/dependencies.props index 5f3f478827..014c60bb3e 100644 --- a/build/dependencies.props +++ b/build/dependencies.props @@ -18,7 +18,7 @@ 2.2.0-preview1-34066 2.2.0-preview1-34066 2.2.0-preview1-34066 - 2.2.0-preview1-34066 + 2.2.0-a-preview1-pool-cleanup-16484 2.2.0-preview1-34066 2.2.0-preview1-34066 2.2.0-preview1-34066 diff --git a/src/Directory.Build.props b/src/Directory.Build.props index 6b85b2cf04..cf4c0f9e56 100644 --- a/src/Directory.Build.props +++ b/src/Directory.Build.props @@ -1,8 +1,5 @@ - - $(DefineConstants);INNER_LOOP - diff --git a/src/Kestrel.Core/Adapter/Internal/LoggingStream.cs b/src/Kestrel.Core/Adapter/Internal/LoggingStream.cs index 0b5fdf3975..f9225b6f48 100644 --- a/src/Kestrel.Core/Adapter/Internal/LoggingStream.cs +++ b/src/Kestrel.Core/Adapter/Internal/LoggingStream.cs @@ -148,6 +148,11 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Adapter.Internal private void Log(string method, ReadOnlySpan buffer) { + if (!_logger.IsEnabled(LogLevel.Debug)) + { + return; + } + var builder = new StringBuilder($"{method}[{buffer.Length}] "); // Write the hex diff --git a/src/Kestrel.Transport.Abstractions/Internal/KestrelMemoryPool.cs b/src/Kestrel.Transport.Abstractions/Internal/KestrelMemoryPool.cs index f53c15d543..b3577f2c0a 100644 --- a/src/Kestrel.Transport.Abstractions/Internal/KestrelMemoryPool.cs +++ b/src/Kestrel.Transport.Abstractions/Internal/KestrelMemoryPool.cs @@ -1,13 +1,26 @@ // 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; namespace Microsoft.AspNetCore.Server.Kestrel.Transport.Abstractions.Internal { public static class KestrelMemoryPool { - public static MemoryPool Create() => new SlabMemoryPool(); + public static MemoryPool Create() + { +#if DEBUG + return new DiagnosticMemoryPool(CreateSlabMemoryPool()); +#else + return CreateSlabMemoryPool(); +#endif + } + + public static MemoryPool CreateSlabMemoryPool() + { + return new SlabMemoryPool(); + } public static readonly int MinimumSegmentSize = 4096; } diff --git a/src/Kestrel.Transport.Libuv/AssemblyInfo.cs b/src/Kestrel.Transport.Libuv/AssemblyInfo.cs new file mode 100644 index 0000000000..29df5a481f --- /dev/null +++ b/src/Kestrel.Transport.Libuv/AssemblyInfo.cs @@ -0,0 +1,6 @@ +// 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.Runtime.CompilerServices; + +[assembly: InternalsVisibleTo("Libuv.FunctionalTests, PublicKey=0024000004800000940000000602000000240000525341310004000001000100f33a29044fa9d740c9b3213a93e57c84b472c84e0b8a0e1ae48e67a9f8f6de9d5f7f3d52ac23e48ac51801f1dc950abe901da34d2a9e3baadb141a17c77ef3c565dd5ee5054b91cf63bb3c6ab83f72ab3aafe93d0fc3c2348b764fafb0b1c0733de51459aeab46580384bf9d74c4e28164b7cde247f891ba07891c9d872ad2bb")] diff --git a/src/Kestrel.Transport.Libuv/Internal/LibuvThread.cs b/src/Kestrel.Transport.Libuv/Internal/LibuvThread.cs index f8145ae22d..43b8f8912e 100644 --- a/src/Kestrel.Transport.Libuv/Internal/LibuvThread.cs +++ b/src/Kestrel.Transport.Libuv/Internal/LibuvThread.cs @@ -38,7 +38,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Transport.Libuv.Internal private readonly object _startSync = new object(); private bool _stopImmediate = false; private bool _initCompleted = false; - private ExceptionDispatchInfo _closeError; + private Exception _closeError; private readonly ILibuvTrace _log; public LibuvThread(LibuvTransport transport) @@ -61,7 +61,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Transport.Libuv.Internal #endif QueueCloseHandle = PostCloseHandle; QueueCloseAsyncHandle = EnqueueCloseHandle; - MemoryPool = KestrelMemoryPool.Create(); + MemoryPool = transport.TransportOptions.MemoryPoolFactory(); WriteReqPool = new WriteReqPool(this, _log); } @@ -82,7 +82,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Transport.Libuv.Internal public List Requests { get; } = new List(); #endif - public ExceptionDispatchInfo FatalError { get { return _closeError; } } + public Exception FatalError => _closeError; public Action, IntPtr> QueueCloseHandle { get; } @@ -133,7 +133,10 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Transport.Libuv.Internal } } - _closeError?.Throw(); + if (_closeError != null) + { + ExceptionDispatchInfo.Capture(_closeError).Throw(); + } } #if DEBUG && !INNER_LOOP @@ -323,14 +326,21 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Transport.Libuv.Internal } catch (Exception ex) { - _closeError = ExceptionDispatchInfo.Capture(ex); + _closeError = ex; // Request shutdown so we can rethrow this exception // in Stop which should be observable. _appLifetime.StopApplication(); } finally { - MemoryPool.Dispose(); + try + { + MemoryPool.Dispose(); + } + catch (Exception ex) + { + _closeError = _closeError == null ? ex : new AggregateException(_closeError, ex); + } WriteReqPool.Dispose(); _threadTcs.SetResult(null); diff --git a/src/Kestrel.Transport.Libuv/LibuvTransportOptions.cs b/src/Kestrel.Transport.Libuv/LibuvTransportOptions.cs index db157d39dd..a040dea87b 100644 --- a/src/Kestrel.Transport.Libuv/LibuvTransportOptions.cs +++ b/src/Kestrel.Transport.Libuv/LibuvTransportOptions.cs @@ -2,6 +2,8 @@ // Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. using System; +using System.Buffers; +using Microsoft.AspNetCore.Server.Kestrel.Transport.Abstractions.Internal; namespace Microsoft.AspNetCore.Server.Kestrel.Transport.Libuv { @@ -18,6 +20,8 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Transport.Libuv /// public int ThreadCount { get; set; } = ProcessorThreadCount; + internal Func> MemoryPoolFactory { get; set; } = () => KestrelMemoryPool.Create(); + private static int ProcessorThreadCount { get diff --git a/src/Kestrel.Transport.Sockets/AssemblyInfo.cs b/src/Kestrel.Transport.Sockets/AssemblyInfo.cs new file mode 100644 index 0000000000..89edc0376a --- /dev/null +++ b/src/Kestrel.Transport.Sockets/AssemblyInfo.cs @@ -0,0 +1,6 @@ +// 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.Runtime.CompilerServices; + +[assembly: InternalsVisibleTo("Sockets.FunctionalTests, PublicKey=0024000004800000940000000602000000240000525341310004000001000100f33a29044fa9d740c9b3213a93e57c84b472c84e0b8a0e1ae48e67a9f8f6de9d5f7f3d52ac23e48ac51801f1dc950abe901da34d2a9e3baadb141a17c77ef3c565dd5ee5054b91cf63bb3c6ab83f72ab3aafe93d0fc3c2348b764fafb0b1c0733de51459aeab46580384bf9d74c4e28164b7cde247f891ba07891c9d872ad2bb")] diff --git a/src/Kestrel.Transport.Sockets/SocketTransport.cs b/src/Kestrel.Transport.Sockets/SocketTransport.cs index 4882b47114..1c17258ca4 100644 --- a/src/Kestrel.Transport.Sockets/SocketTransport.cs +++ b/src/Kestrel.Transport.Sockets/SocketTransport.cs @@ -22,7 +22,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Transport.Sockets { private static readonly PipeScheduler[] ThreadPoolSchedulerArray = new PipeScheduler[] { PipeScheduler.ThreadPool }; - private readonly MemoryPool _memoryPool = KestrelMemoryPool.Create(); + private readonly MemoryPool _memoryPool; private readonly IEndPointInformation _endPointInformation; private readonly IConnectionDispatcher _dispatcher; private readonly IApplicationLifetime _appLifetime; @@ -39,7 +39,8 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Transport.Sockets IConnectionDispatcher dispatcher, IApplicationLifetime applicationLifetime, int ioQueueCount, - ISocketsTrace trace) + ISocketsTrace trace, + MemoryPool memoryPool) { Debug.Assert(endPointInformation != null); Debug.Assert(endPointInformation.Type == ListenType.IPEndPoint); @@ -51,6 +52,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Transport.Sockets _dispatcher = dispatcher; _appLifetime = applicationLifetime; _trace = trace; + _memoryPool = memoryPool; if (ioQueueCount > 0) { diff --git a/src/Kestrel.Transport.Sockets/SocketTransportFactory.cs b/src/Kestrel.Transport.Sockets/SocketTransportFactory.cs index 7ddd1d918d..2e0e8170ef 100644 --- a/src/Kestrel.Transport.Sockets/SocketTransportFactory.cs +++ b/src/Kestrel.Transport.Sockets/SocketTransportFactory.cs @@ -57,7 +57,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Transport.Sockets throw new ArgumentNullException(nameof(dispatcher)); } - return new SocketTransport(endPointInformation, dispatcher, _appLifetime, _options.IOQueueCount, _trace); + return new SocketTransport(endPointInformation, dispatcher, _appLifetime, _options.IOQueueCount, _trace, _options.MemoryPoolFactory()); } } } diff --git a/src/Kestrel.Transport.Sockets/SocketTransportOptions.cs b/src/Kestrel.Transport.Sockets/SocketTransportOptions.cs index b6cec0a6d7..2dad914423 100644 --- a/src/Kestrel.Transport.Sockets/SocketTransportOptions.cs +++ b/src/Kestrel.Transport.Sockets/SocketTransportOptions.cs @@ -2,6 +2,8 @@ // Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. using System; +using System.Buffers; +using Microsoft.AspNetCore.Server.Kestrel.Transport.Abstractions.Internal; namespace Microsoft.AspNetCore.Server.Kestrel.Transport.Sockets { @@ -14,5 +16,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Transport.Sockets /// Defaults to rounded down and clamped between 1 and 16. /// public int IOQueueCount { get; set; } = Math.Min(Environment.ProcessorCount, 16); + + internal Func> MemoryPoolFactory { get; set; } = () => KestrelMemoryPool.Create(); } } diff --git a/test/Kestrel.Core.Tests/Http2ConnectionTests.cs b/test/Kestrel.Core.Tests/Http2ConnectionTests.cs index 341c9b21e6..43cd9078d2 100644 --- a/test/Kestrel.Core.Tests/Http2ConnectionTests.cs +++ b/test/Kestrel.Core.Tests/Http2ConnectionTests.cs @@ -287,6 +287,10 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Tests public void Dispose() { + _pair.Application.Input.Complete(); + _pair.Application.Output.Complete(); + _pair.Transport.Input.Complete(); + _pair.Transport.Output.Complete(); _memoryPool.Dispose(); } @@ -1213,7 +1217,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Tests withLength: 0, withFlags: (byte)Http2DataFrameFlags.END_STREAM, withStreamId: 1); - + await StopConnectionAsync(expectedLastStreamId: 1, ignoreNonGoAwayFrames: false); _hpackDecoder.Decode(headersFrame.HeadersPayload, endHeaders: false, handler: this); diff --git a/test/Kestrel.Core.Tests/PipelineExtensionTests.cs b/test/Kestrel.Core.Tests/PipelineExtensionTests.cs index e3a89832da..11f7209ac2 100644 --- a/test/Kestrel.Core.Tests/PipelineExtensionTests.cs +++ b/test/Kestrel.Core.Tests/PipelineExtensionTests.cs @@ -26,6 +26,8 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Tests public void Dispose() { + _pipe.Reader.Complete(); + _pipe.Writer.Complete(); _memoryPool.Dispose(); } diff --git a/test/Kestrel.Core.Tests/TestInput.cs b/test/Kestrel.Core.Tests/TestInput.cs index 1295121548..1916b62753 100644 --- a/test/Kestrel.Core.Tests/TestInput.cs +++ b/test/Kestrel.Core.Tests/TestInput.cs @@ -68,6 +68,10 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Tests public void Dispose() { + Application.Input.Complete(); + Application.Output.Complete(); + Transport.Input.Complete(); + Transport.Output.Complete(); _memoryPool.Dispose(); } } diff --git a/test/Kestrel.FunctionalTests/ChunkedRequestTests.cs b/test/Kestrel.FunctionalTests/ChunkedRequestTests.cs index 8dba8331e1..3bc0c9d286 100644 --- a/test/Kestrel.FunctionalTests/ChunkedRequestTests.cs +++ b/test/Kestrel.FunctionalTests/ChunkedRequestTests.cs @@ -12,11 +12,16 @@ using Microsoft.AspNetCore.Server.Kestrel.Core; using Microsoft.AspNetCore.Testing; using Microsoft.Extensions.Logging.Testing; using Xunit; +using Xunit.Abstractions; namespace Microsoft.AspNetCore.Server.Kestrel.FunctionalTests { public class ChunkedRequestTests : LoggedTest { + public ChunkedRequestTests(ITestOutputHelper output) : base(output) + { + } + public static TheoryData ConnectionAdapterData => new TheoryData { new ListenOptions(new IPEndPoint(IPAddress.Loopback, 0)), @@ -88,7 +93,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.FunctionalTests [MemberData(nameof(ConnectionAdapterData))] public async Task Http10KeepAliveTransferEncoding(ListenOptions listenOptions) { - var testContext = new TestServiceContext(LoggerFactory); + var testContext = new TestServiceContext(); using (var server = new TestServer(AppChunked, testContext, listenOptions)) { diff --git a/test/Kestrel.FunctionalTests/DiagnosticMemoryPoolFactory.cs b/test/Kestrel.FunctionalTests/DiagnosticMemoryPoolFactory.cs new file mode 100644 index 0000000000..e50e1ed305 --- /dev/null +++ b/test/Kestrel.FunctionalTests/DiagnosticMemoryPoolFactory.cs @@ -0,0 +1,46 @@ +// 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.Generic; +using System.Linq; +using System.Threading.Tasks; +using Microsoft.AspNetCore.Server.Kestrel.Transport.Abstractions.Internal; + +namespace Microsoft.AspNetCore.Server.Kestrel.FunctionalTests +{ + public class DiagnosticMemoryPoolFactory + { + private readonly bool _allowLateReturn; + + private readonly bool _rentTracking; + + private readonly List _pools; + + public DiagnosticMemoryPoolFactory(bool allowLateReturn = false, bool rentTracking = false) + { + _allowLateReturn = allowLateReturn; + _rentTracking = rentTracking; + _pools = new List(); + } + + public MemoryPool Create() + { + lock (_pools) + { + var pool = new DiagnosticMemoryPool(KestrelMemoryPool.CreateSlabMemoryPool(),_allowLateReturn, _rentTracking); + _pools.Add(pool); + return pool; + } + } + + public Task WhenAllBlocksReturned(TimeSpan span) + { + lock (_pools) + { + return Task.WhenAll(_pools.Select(p=>p.WhenAllBlocksReturnedAsync(span))); + } + } + } +} \ No newline at end of file diff --git a/test/Kestrel.FunctionalTests/MaxRequestBufferSizeTests.cs b/test/Kestrel.FunctionalTests/MaxRequestBufferSizeTests.cs index f4b8db2168..de3254e5da 100644 --- a/test/Kestrel.FunctionalTests/MaxRequestBufferSizeTests.cs +++ b/test/Kestrel.FunctionalTests/MaxRequestBufferSizeTests.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.Collections.Generic; using System.IO; using System.Linq; @@ -90,7 +91,9 @@ namespace Microsoft.AspNetCore.Server.Kestrel.FunctionalTests var clientFinishedSendingRequestBody = new TaskCompletionSource(); var lastBytesWritten = DateTime.MaxValue; - using (var host = StartWebHost(maxRequestBufferSize, data, connectionAdapter, startReadingRequestBody, clientFinishedSendingRequestBody)) + var memoryPoolFactory = new DiagnosticMemoryPoolFactory(allowLateReturn: true); + + using (var host = StartWebHost(maxRequestBufferSize, data, connectionAdapter, startReadingRequestBody, clientFinishedSendingRequestBody, memoryPoolFactory.Create)) { var port = host.GetPort(); using (var socket = CreateSocket(port)) @@ -167,6 +170,8 @@ namespace Microsoft.AspNetCore.Server.Kestrel.FunctionalTests } } } + + await memoryPoolFactory.WhenAllBlocksReturned(TestConstants.DefaultTimeout); } [Fact] @@ -182,7 +187,9 @@ namespace Microsoft.AspNetCore.Server.Kestrel.FunctionalTests var clientFinishedSendingRequestBody = new TaskCompletionSource(); var lastBytesWritten = DateTime.MaxValue; - using (var host = StartWebHost(16 * 1024, data, false, startReadingRequestBody, clientFinishedSendingRequestBody)) + var memoryPoolFactory = new DiagnosticMemoryPoolFactory(allowLateReturn: true); + + using (var host = StartWebHost(16 * 1024, data, false, startReadingRequestBody, clientFinishedSendingRequestBody, memoryPoolFactory.Create)) { var port = host.GetPort(); using (var socket = CreateSocket(port)) @@ -239,15 +246,20 @@ namespace Microsoft.AspNetCore.Server.Kestrel.FunctionalTests host.Dispose(); } } + // Allow appfunc to unblock + startReadingRequestBody.SetResult(null); + clientFinishedSendingRequestBody.SetResult(null); + await memoryPoolFactory.WhenAllBlocksReturned(TestConstants.DefaultTimeout); } private IWebHost StartWebHost(long? maxRequestBufferSize, byte[] expectedBody, bool useConnectionAdapter, TaskCompletionSource startReadingRequestBody, - TaskCompletionSource clientFinishedSendingRequestBody) + TaskCompletionSource clientFinishedSendingRequestBody, + Func> memoryPoolFactory = null) { - var host = TransportSelector.GetWebHostBuilder() + var host = TransportSelector.GetWebHostBuilder(memoryPoolFactory) .ConfigureServices(AddTestLogging) .UseKestrel(options => { diff --git a/test/Kestrel.FunctionalTests/RequestTests.cs b/test/Kestrel.FunctionalTests/RequestTests.cs index e7cadd4ae8..3b6c2fc0d5 100644 --- a/test/Kestrel.FunctionalTests/RequestTests.cs +++ b/test/Kestrel.FunctionalTests/RequestTests.cs @@ -590,9 +590,9 @@ namespace Microsoft.AspNetCore.Server.Kestrel.FunctionalTests [InlineData("http://localhost/path%20with%20space?q=abc%20123", "/path with space", "abc 123")] public async Task CanHandleRequestsWithUrlInAbsoluteForm(string requestUrl, string expectedPath, string queryValue) { - var pathTcs = new TaskCompletionSource(); - var rawTargetTcs = new TaskCompletionSource(); - var queryTcs = new TaskCompletionSource(); + var pathTcs = new TaskCompletionSource(TaskCreationOptions.RunContinuationsAsynchronously); + var rawTargetTcs = new TaskCompletionSource(TaskCreationOptions.RunContinuationsAsynchronously); + var queryTcs = new TaskCompletionSource(TaskCreationOptions.RunContinuationsAsynchronously); using (var server = new TestServer(async context => { @@ -1031,8 +1031,8 @@ namespace Microsoft.AspNetCore.Server.Kestrel.FunctionalTests { var testContext = new TestServiceContext(LoggerFactory); - var readTcs = new TaskCompletionSource(); - var registrationTcs = new TaskCompletionSource(); + var readTcs = new TaskCompletionSource(TaskCreationOptions.RunContinuationsAsynchronously); + var registrationTcs = new TaskCompletionSource(TaskCreationOptions.RunContinuationsAsynchronously); var requestId = 0; using (var server = new TestServer(async httpContext => diff --git a/test/Kestrel.FunctionalTests/ResponseTests.cs b/test/Kestrel.FunctionalTests/ResponseTests.cs index cec6547cfd..9e228140cc 100644 --- a/test/Kestrel.FunctionalTests/ResponseTests.cs +++ b/test/Kestrel.FunctionalTests/ResponseTests.cs @@ -33,6 +33,7 @@ using Microsoft.Extensions.Logging.Testing; using Microsoft.Extensions.Primitives; using Moq; using Xunit; +using Xunit.Abstractions; namespace Microsoft.AspNetCore.Server.Kestrel.FunctionalTests { @@ -145,7 +146,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.FunctionalTests public async Task OnCompleteCalledEvenWhenOnStartingNotCalled() { var onStartingCalled = false; - var onCompletedTcs = new TaskCompletionSource(); + var onCompletedTcs = new TaskCompletionSource(TaskCreationOptions.RunContinuationsAsynchronously); var hostBuilder = TransportSelector.GetWebHostBuilder() .UseKestrel() @@ -341,7 +342,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.FunctionalTests [Fact] public async Task OnCompletedShouldNotBlockAResponse() { - var delayTcs = new TaskCompletionSource(); + var delayTcs = new TaskCompletionSource(TaskCreationOptions.RunContinuationsAsynchronously); var hostBuilder = TransportSelector.GetWebHostBuilder() .UseKestrel() .UseUrls("http://127.0.0.1:0/") @@ -375,7 +376,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.FunctionalTests [Fact] public async Task InvalidChunkedEncodingInRequestShouldNotBlockOnCompleted() { - var onCompletedTcs = new TaskCompletionSource(); + var onCompletedTcs = new TaskCompletionSource(TaskCreationOptions.RunContinuationsAsynchronously); using (var server = new TestServer(httpContext => { @@ -418,7 +419,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.FunctionalTests mockHttpContextFactory.Setup(f => f.Create(It.IsAny())) .Returns(fc => new DefaultHttpContext(fc)); - var disposedTcs = new TaskCompletionSource(); + var disposedTcs = new TaskCompletionSource(TaskCreationOptions.RunContinuationsAsynchronously); mockHttpContextFactory.Setup(f => f.Dispose(It.IsAny())) .Callback(c => { @@ -619,7 +620,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.FunctionalTests { const string response = "hello, world"; - var logTcs = new TaskCompletionSource(); + var logTcs = new TaskCompletionSource(TaskCreationOptions.RunContinuationsAsynchronously); var mockKestrelTrace = new Mock(); mockKestrelTrace .Setup(trace => trace.ConnectionHeadResponseBodyWrite(It.IsAny(), response.Length)) @@ -808,7 +809,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.FunctionalTests [Fact] public async Task WhenAppWritesLessThanContentLengthErrorLogged() { - var logTcs = new TaskCompletionSource(); + var logTcs = new TaskCompletionSource(TaskCreationOptions.RunContinuationsAsynchronously); var mockTrace = new Mock(); mockTrace .Setup(trace => trace.ApplicationError(It.IsAny(), It.IsAny(), It.IsAny())) @@ -1182,7 +1183,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.FunctionalTests { var connectionClosed = new ManualResetEventSlim(); var requestStarted = new ManualResetEventSlim(); - var tcs = new TaskCompletionSource(); + var tcs = new TaskCompletionSource(TaskCreationOptions.RunContinuationsAsynchronously); using (var server = new TestServer(async httpContext => { @@ -2235,7 +2236,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.FunctionalTests // Ensure string is long enough to disable write-behind buffering var largeString = new string('a', maxBytesPreCompleted + 1); - var writeTcs = new TaskCompletionSource(); + var writeTcs = new TaskCompletionSource(TaskCreationOptions.RunContinuationsAsynchronously); var requestAbortedWh = new ManualResetEventSlim(); var requestStartWh = new ManualResetEventSlim(); @@ -2400,7 +2401,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.FunctionalTests var testContext= new TestServiceContext(LoggerFactory); var callOrder = new Stack(); - var onStartingTcs = new TaskCompletionSource(); + var onStartingTcs = new TaskCompletionSource(TaskCreationOptions.RunContinuationsAsynchronously); using (var server = new TestServer(async context => { @@ -2452,7 +2453,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.FunctionalTests var testContext= new TestServiceContext(LoggerFactory); var callOrder = new Stack(); - var onCompletedTcs = new TaskCompletionSource(); + var onCompletedTcs = new TaskCompletionSource(TaskCreationOptions.RunContinuationsAsynchronously); using (var server = new TestServer(async context => { diff --git a/test/Kestrel.Transport.Libuv.FunctionalTests/TransportSelector.cs b/test/Kestrel.Transport.Libuv.FunctionalTests/TransportSelector.cs index db778d603b..98a41b7760 100644 --- a/test/Kestrel.Transport.Libuv.FunctionalTests/TransportSelector.cs +++ b/test/Kestrel.Transport.Libuv.FunctionalTests/TransportSelector.cs @@ -1,15 +1,17 @@ // 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 Microsoft.AspNetCore.Hosting; namespace Microsoft.AspNetCore.Server.Kestrel.FunctionalTests { public static class TransportSelector { - public static IWebHostBuilder GetWebHostBuilder() + public static IWebHostBuilder GetWebHostBuilder(Func> memoryPoolFactory = null) { - return new WebHostBuilder().UseLibuv().ConfigureServices(TestServer.RemoveDevCert); + return new WebHostBuilder().UseLibuv(options => { options.MemoryPoolFactory = memoryPoolFactory ?? options.MemoryPoolFactory; }).ConfigureServices(TestServer.RemoveDevCert); } } } diff --git a/test/Kestrel.Transport.Libuv.Tests/LibuvConnectionTests.cs b/test/Kestrel.Transport.Libuv.Tests/LibuvConnectionTests.cs index 7a82e6da15..8f856bc508 100644 --- a/test/Kestrel.Transport.Libuv.Tests/LibuvConnectionTests.cs +++ b/test/Kestrel.Transport.Libuv.Tests/LibuvConnectionTests.cs @@ -24,7 +24,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Transport.Libuv.Tests var transportContext = new TestLibuvTransportContext() { ConnectionDispatcher = mockConnectionDispatcher }; var transport = new LibuvTransport(mockLibuv, transportContext, null); var thread = new LibuvThread(transport); - + Task connectionTask = null; try { await thread.StartAsync(); @@ -36,7 +36,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Transport.Libuv.Tests }; var socket = new MockSocket(mockLibuv, Thread.CurrentThread.ManagedThreadId, transportContext.Log); var connection = new LibuvConnection(listenerContext, socket); - _ = connection.Start(); + connectionTask = connection.Start(); mockLibuv.AllocCallback(socket.InternalGetHandle(), 2048, out var ignored); mockLibuv.ReadCallback(socket.InternalGetHandle(), 0, ref ignored); @@ -47,6 +47,10 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Transport.Libuv.Tests } finally { + mockConnectionDispatcher.Input.Reader.Complete(); + mockConnectionDispatcher.Output.Writer.Complete(); + await connectionTask; + await thread.StopAsync(TimeSpan.FromSeconds(5)); } } @@ -108,6 +112,9 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Transport.Libuv.Tests } finally { + mockConnectionDispatcher.Input.Reader.Complete(); + mockConnectionDispatcher.Output.Writer.Complete(); + await thread.StopAsync(TimeSpan.FromSeconds(5)); } } @@ -189,6 +196,9 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Transport.Libuv.Tests } finally { + mockConnectionDispatcher.Input.Reader.Complete(); + mockConnectionDispatcher.Output.Writer.Complete(); + await thread.StopAsync(TimeSpan.FromSeconds(5)); } } @@ -202,6 +212,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Transport.Libuv.Tests var transport = new LibuvTransport(mockLibuv, transportContext, null); var thread = new LibuvThread(transport); + Task connectionTask = null; try { await thread.StartAsync(); @@ -213,7 +224,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Transport.Libuv.Tests }; var socket = new MockSocket(mockLibuv, Thread.CurrentThread.ManagedThreadId, transportContext.Log); var connection = new LibuvConnection(listenerContext, socket); - _ = connection.Start(); + connectionTask = connection.Start(); var ignored = new LibuvFunctions.uv_buf_t(); mockLibuv.ReadCallback(socket.InternalGetHandle(), TestConstants.EOF, ref ignored); @@ -224,6 +235,10 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Transport.Libuv.Tests } finally { + mockConnectionDispatcher.Input.Reader.Complete(); + mockConnectionDispatcher.Output.Writer.Complete(); + await connectionTask; + await thread.StopAsync(TimeSpan.FromSeconds(5)); } } diff --git a/test/Kestrel.Transport.Libuv.Tests/LibuvTransportTests.cs b/test/Kestrel.Transport.Libuv.Tests/LibuvTransportTests.cs index 4d2be2380c..a93967b37d 100644 --- a/test/Kestrel.Transport.Libuv.Tests/LibuvTransportTests.cs +++ b/test/Kestrel.Transport.Libuv.Tests/LibuvTransportTests.cs @@ -1,12 +1,14 @@ // 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.Collections.Generic; using System.Linq; using System.Net; using System.Net.Http; using System.Net.Sockets; using System.Text; +using System.Threading; using System.Threading.Tasks; using Microsoft.AspNetCore.Http; using Microsoft.AspNetCore.Server.Kestrel.Core; @@ -85,6 +87,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Transport.Libuv.Tests } } + Assert.True(await serviceContext.ConnectionManager.CloseAllConnectionsAsync(new CancellationTokenSource(TestConstants.DefaultTimeout).Token)); await transport.UnbindAsync(); await transport.StopAsync(); } diff --git a/test/Kestrel.Transport.Sockets.FunctionalTests/TransportSelector.cs b/test/Kestrel.Transport.Sockets.FunctionalTests/TransportSelector.cs index 52d2b0a4a8..9d5ea6ede5 100644 --- a/test/Kestrel.Transport.Sockets.FunctionalTests/TransportSelector.cs +++ b/test/Kestrel.Transport.Sockets.FunctionalTests/TransportSelector.cs @@ -1,15 +1,17 @@ // 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 Microsoft.AspNetCore.Hosting; namespace Microsoft.AspNetCore.Server.Kestrel.FunctionalTests { public static class TransportSelector { - public static IWebHostBuilder GetWebHostBuilder() + public static IWebHostBuilder GetWebHostBuilder(Func> memoryPoolFactory = null) { - return new WebHostBuilder().UseSockets().ConfigureServices(TestServer.RemoveDevCert); + return new WebHostBuilder().UseSockets(options => { options.MemoryPoolFactory = memoryPoolFactory ?? options.MemoryPoolFactory; }).ConfigureServices(TestServer.RemoveDevCert); } } } diff --git a/test/shared/CompositeKestrelTrace.cs b/test/shared/CompositeKestrelTrace.cs new file mode 100644 index 0000000000..8a7a040d42 --- /dev/null +++ b/test/shared/CompositeKestrelTrace.cs @@ -0,0 +1,187 @@ +// Copyright (c) .NET Foundation. All rights reserved. +// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. + +using System; +using System.IO.Pipelines; +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.Core.Internal.Http2; +using Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http2.HPack; +using Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Infrastructure; +using Microsoft.Extensions.Logging; + +namespace Microsoft.AspNetCore.Testing +{ + public class CompositeKestrelTrace: IKestrelTrace + { + private readonly IKestrelTrace _trace1; + private readonly IKestrelTrace _trace2; + + public CompositeKestrelTrace(IKestrelTrace kestrelTrace, KestrelTrace kestrelTrace1) + { + _trace1 = kestrelTrace; + _trace2 = kestrelTrace1; + } + + public void Log(LogLevel logLevel, EventId eventId, TState state, Exception exception, Func formatter) + { + _trace1.Log(logLevel, eventId, state, exception, formatter); + _trace2.Log(logLevel, eventId, state, exception, formatter); + } + + public bool IsEnabled(LogLevel logLevel) + { + return _trace1.IsEnabled(logLevel) || _trace2.IsEnabled(logLevel); + } + + public IDisposable BeginScope(TState state) + { + return _trace1.BeginScope(state); + } + + public void ConnectionStart(string connectionId) + { + _trace1.ConnectionStart(connectionId); + _trace2.ConnectionStart(connectionId); + } + + public void ConnectionStop(string connectionId) + { + _trace1.ConnectionStop(connectionId); + _trace2.ConnectionStop(connectionId); + } + + public void ConnectionPause(string connectionId) + { + _trace1.ConnectionPause(connectionId); + _trace2.ConnectionPause(connectionId); + } + + public void ConnectionResume(string connectionId) + { + _trace1.ConnectionResume(connectionId); + _trace2.ConnectionResume(connectionId); + } + + public void ConnectionRejected(string connectionId) + { + _trace1.ConnectionRejected(connectionId); + _trace2.ConnectionRejected(connectionId); + } + + public void ConnectionKeepAlive(string connectionId) + { + _trace1.ConnectionKeepAlive(connectionId); + _trace2.ConnectionKeepAlive(connectionId); + } + + public void ConnectionDisconnect(string connectionId) + { + _trace1.ConnectionDisconnect(connectionId); + _trace2.ConnectionDisconnect(connectionId); + } + + public void RequestProcessingError(string connectionId, Exception ex) + { + _trace1.RequestProcessingError(connectionId, ex); + _trace2.RequestProcessingError(connectionId, ex); + } + + public void ConnectionHeadResponseBodyWrite(string connectionId, long count) + { + _trace1.ConnectionHeadResponseBodyWrite(connectionId, count); + _trace2.ConnectionHeadResponseBodyWrite(connectionId, count); + } + + public void NotAllConnectionsClosedGracefully() + { + _trace1.NotAllConnectionsClosedGracefully(); + _trace2.NotAllConnectionsClosedGracefully(); + } + + public void ConnectionBadRequest(string connectionId, BadHttpRequestException ex) + { + _trace1.ConnectionBadRequest(connectionId, ex); + _trace2.ConnectionBadRequest(connectionId, ex); + } + + public void ApplicationError(string connectionId, string traceIdentifier, Exception ex) + { + _trace1.ApplicationError(connectionId, traceIdentifier, ex); + _trace2.ApplicationError(connectionId, traceIdentifier, ex); + } + + public void NotAllConnectionsAborted() + { + _trace1.NotAllConnectionsAborted(); + _trace2.NotAllConnectionsAborted(); + } + + public void HeartbeatSlow(TimeSpan interval, DateTimeOffset now) + { + _trace1.HeartbeatSlow(interval, now); + _trace2.HeartbeatSlow(interval, now); + } + + public void ApplicationNeverCompleted(string connectionId) + { + _trace1.ApplicationNeverCompleted(connectionId); + _trace2.ApplicationNeverCompleted(connectionId); + } + + public void RequestBodyStart(string connectionId, string traceIdentifier) + { + _trace1.RequestBodyStart(connectionId, traceIdentifier); + _trace2.RequestBodyStart(connectionId, traceIdentifier); + } + + public void RequestBodyDone(string connectionId, string traceIdentifier) + { + _trace1.RequestBodyDone(connectionId, traceIdentifier); + _trace2.RequestBodyDone(connectionId, traceIdentifier); + } + + public void RequestBodyNotEntirelyRead(string connectionId, string traceIdentifier) + { + _trace1.RequestBodyNotEntirelyRead(connectionId, traceIdentifier); + _trace2.RequestBodyNotEntirelyRead(connectionId, traceIdentifier); + } + + public void RequestBodyDrainTimedOut(string connectionId, string traceIdentifier) + { + _trace1.RequestBodyDrainTimedOut(connectionId, traceIdentifier); + _trace2.RequestBodyDrainTimedOut(connectionId, traceIdentifier); + } + + public void RequestBodyMininumDataRateNotSatisfied(string connectionId, string traceIdentifier, double rate) + { + _trace1.RequestBodyMininumDataRateNotSatisfied(connectionId, traceIdentifier, rate); + _trace2.RequestBodyMininumDataRateNotSatisfied(connectionId, traceIdentifier, rate); + } + + public void ResponseMininumDataRateNotSatisfied(string connectionId, string traceIdentifier) + { + _trace1.ResponseMininumDataRateNotSatisfied(connectionId, traceIdentifier); + _trace2.ResponseMininumDataRateNotSatisfied(connectionId, traceIdentifier); + } + + public void Http2ConnectionError(string connectionId, Http2ConnectionErrorException ex) + { + _trace1.Http2ConnectionError(connectionId, ex); + _trace2.Http2ConnectionError(connectionId, ex); + } + + public void Http2StreamError(string connectionId, Http2StreamErrorException ex) + { + _trace1.Http2StreamError(connectionId, ex); + _trace2.Http2StreamError(connectionId, ex); + } + + public void HPackDecodingError(string connectionId, int streamId, HPackDecodingException ex) + { + _trace1.HPackDecodingError(connectionId, streamId, ex); + _trace2.HPackDecodingError(connectionId, streamId, ex); + } + } +} diff --git a/test/shared/TestApplicationErrorLogger.cs b/test/shared/TestApplicationErrorLogger.cs index 731c7b8b94..2f05ec174c 100644 --- a/test/shared/TestApplicationErrorLogger.cs +++ b/test/shared/TestApplicationErrorLogger.cs @@ -44,15 +44,16 @@ namespace Microsoft.AspNetCore.Testing public void Log(LogLevel logLevel, EventId eventId, TState state, Exception exception, Func formatter) { + var exceptionIsIgnored = IgnoredExceptions.Contains(exception?.GetType()); #if true - if (logLevel == LogLevel.Critical && ThrowOnCriticalErrors && !IgnoredExceptions.Contains(exception.GetType())) + if (logLevel == LogLevel.Critical && ThrowOnCriticalErrors && !exceptionIsIgnored) #endif { var log = $"Log {logLevel}[{eventId}]: {formatter(state, exception)} {exception}"; Console.WriteLine(log); - if (logLevel == LogLevel.Critical && ThrowOnCriticalErrors && !IgnoredExceptions.Contains(exception.GetType())) + if (logLevel == LogLevel.Critical && ThrowOnCriticalErrors && !exceptionIsIgnored) { throw new Exception($"Unexpected critical error. {log}", exception); } diff --git a/test/shared/TestServiceContext.cs b/test/shared/TestServiceContext.cs index d662ee9686..d2d4252dc6 100644 --- a/test/shared/TestServiceContext.cs +++ b/test/shared/TestServiceContext.cs @@ -1,10 +1,13 @@ // Copyright (c) .NET Foundation. All rights reserved. // Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. +using System; using System.IO.Pipelines; 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.Core.Internal.Http2; +using Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http2.HPack; using Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Infrastructure; using Microsoft.Extensions.Logging; @@ -22,13 +25,18 @@ namespace Microsoft.AspNetCore.Testing } public TestServiceContext(ILoggerFactory loggerFactory) - : this(loggerFactory, new KestrelTrace(loggerFactory.CreateLogger("Microsoft.AspNetCore.Server.Kestrel"))) { + Initialize(loggerFactory, CreateLoggingTrace(loggerFactory)); } public TestServiceContext(ILoggerFactory loggerFactory, IKestrelTrace kestrelTrace) { - Initialize(loggerFactory, kestrelTrace); + Initialize(loggerFactory, new CompositeKestrelTrace(kestrelTrace, CreateLoggingTrace(loggerFactory))); + } + + private static KestrelTrace CreateLoggingTrace(ILoggerFactory loggerFactory) + { + return new KestrelTrace(loggerFactory.CreateLogger("Microsoft.AspNetCore.Server.Kestrel")); } private void Initialize(ILoggerFactory loggerFactory, IKestrelTrace kestrelTrace) diff --git a/test/xunit.runner.json b/test/xunit.runner.json index 3a5192e57d..e3984f4384 100644 --- a/test/xunit.runner.json +++ b/test/xunit.runner.json @@ -2,5 +2,6 @@ "$schema": "http://json.schemastore.org/xunit.runner.schema", "appDomain": "denied", "methodDisplay": "method", - "longRunningTestSeconds": 60 + "longRunningTestSeconds": 60, + "maxParallelThreads": -1 }