From 9969e99ef4170085dae20aab3785d8d15e510dfd Mon Sep 17 00:00:00 2001 From: Dylan Dmitri Gray <d.dylan.g@gmail.com> Date: Mon, 27 May 2019 14:46:14 -0700 Subject: [PATCH] Dylan/request throttle (#10413) * request throttling -- initial implementation * prevented semaphore leak; added xml docs * small doc fixes * reference document * Added internals folder, added structured logging, * removed typo'd dependency * no default MaxConcurrentRequests; other polishing * renamed SemaphoreWrapper->RequestQueue; cleanup * moved SyncPoint; prevented possible semaphore leak * adjusting feedback * regen refs * Final changes! --- ...rosoft.AspNetCore.RequestThrottling.csproj | 4 +- ...NetCore.RequestThrottling.netcoreapp3.0.cs | 21 ++++ .../sample/RequestThrottlingSample.csproj | 4 +- .../RequestThrottling/sample/Startup.cs | 19 ++- .../src/Internal/RequestQueue.cs | 64 ++++++++++ ...rosoft.AspNetCore.RequestThrottling.csproj | 8 +- .../src/RequestThrottlingExtensions.cs | 29 +++++ .../src/RequestThrottlingMiddleware.cs | 116 ++++++++++++++++++ .../src/RequestThrottlingOptions.cs | 19 +++ .../RequestThrottling/src/SemaphoreWrapper.cs | 38 ------ ....AspNetCore.RequestThrottling.Tests.csproj | 8 +- .../RequestThrottling/test/MiddlewareTests.cs | 86 +++++++++++++ ...reWrapperTests.cs => RequestQueueTests.cs} | 42 ++++--- .../RequestThrottling/test/TestUtils.cs | 28 +++++ .../samples/ResponseCachingSample/Startup.cs | 2 +- ...icrosoft.AspNetCore.ResponseCaching.csproj | 2 +- .../Microsoft.AspNetCore.ANCMSymbols.csproj | 10 ++ ...ft.AspNetCore.ANCMSymbols.netcoreapp3.0.cs | 3 + .../SyncPoint}/SyncPoint.cs | 2 +- ...HttpConnectionTests.ConnectionLifecycle.cs | 5 +- .../HubConnectionTests.ConnectionLifecycle.cs | 1 + ...oft.AspNetCore.SignalR.Client.Tests.csproj | 3 +- .../ServerSentEventsTransportTests.cs | 1 + .../test/HttpConnectionDispatcherTests.cs | 1 + ...t.AspNetCore.Http.Connections.Tests.csproj | 3 +- .../Microsoft.AspNetCore.SignalR.Tests.csproj | 7 +- .../SignalR/test/SerializedHubMessageTests.cs | 1 + 27 files changed, 455 insertions(+), 72 deletions(-) create mode 100644 src/Middleware/RequestThrottling/src/Internal/RequestQueue.cs create mode 100644 src/Middleware/RequestThrottling/src/RequestThrottlingExtensions.cs create mode 100644 src/Middleware/RequestThrottling/src/RequestThrottlingMiddleware.cs create mode 100644 src/Middleware/RequestThrottling/src/RequestThrottlingOptions.cs delete mode 100644 src/Middleware/RequestThrottling/src/SemaphoreWrapper.cs create mode 100644 src/Middleware/RequestThrottling/test/MiddlewareTests.cs rename src/Middleware/RequestThrottling/test/{SemaphoreWrapperTests.cs => RequestQueueTests.cs} (60%) create mode 100644 src/Middleware/RequestThrottling/test/TestUtils.cs create mode 100644 src/Servers/IIS/AspNetCoreModuleV2/ref/Microsoft.AspNetCore.ANCMSymbols.csproj create mode 100644 src/Servers/IIS/AspNetCoreModuleV2/ref/Microsoft.AspNetCore.ANCMSymbols.netcoreapp3.0.cs rename src/{SignalR/common/testassets/Tests.Utils => Shared/SyncPoint}/SyncPoint.cs (98%) diff --git a/src/Middleware/RequestThrottling/ref/Microsoft.AspNetCore.RequestThrottling.csproj b/src/Middleware/RequestThrottling/ref/Microsoft.AspNetCore.RequestThrottling.csproj index 0a1bcdd0b91..8e8c1dfce6c 100644 --- a/src/Middleware/RequestThrottling/ref/Microsoft.AspNetCore.RequestThrottling.csproj +++ b/src/Middleware/RequestThrottling/ref/Microsoft.AspNetCore.RequestThrottling.csproj @@ -5,6 +5,8 @@ </PropertyGroup> <ItemGroup Condition="'$(TargetFramework)' == 'netcoreapp3.0'"> <Compile Include="Microsoft.AspNetCore.RequestThrottling.netcoreapp3.0.cs" /> - + <Reference Include="Microsoft.AspNetCore.Http.Abstractions" /> + <Reference Include="Microsoft.Extensions.Logging.Abstractions" /> + <Reference Include="Microsoft.Extensions.Options" /> </ItemGroup> </Project> diff --git a/src/Middleware/RequestThrottling/ref/Microsoft.AspNetCore.RequestThrottling.netcoreapp3.0.cs b/src/Middleware/RequestThrottling/ref/Microsoft.AspNetCore.RequestThrottling.netcoreapp3.0.cs index 618082bc4a8..b5a3acf406a 100644 --- a/src/Middleware/RequestThrottling/ref/Microsoft.AspNetCore.RequestThrottling.netcoreapp3.0.cs +++ b/src/Middleware/RequestThrottling/ref/Microsoft.AspNetCore.RequestThrottling.netcoreapp3.0.cs @@ -1,3 +1,24 @@ // 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. +namespace Microsoft.AspNetCore.Builder +{ + public static partial class RequestThrottlingExtensions + { + public static Microsoft.AspNetCore.Builder.IApplicationBuilder UseRequestThrottling(this Microsoft.AspNetCore.Builder.IApplicationBuilder app) { throw null; } + } +} +namespace Microsoft.AspNetCore.RequestThrottling +{ + public partial class RequestThrottlingMiddleware + { + public RequestThrottlingMiddleware(Microsoft.AspNetCore.Http.RequestDelegate next, Microsoft.Extensions.Logging.ILoggerFactory loggerFactory, Microsoft.Extensions.Options.IOptions<Microsoft.AspNetCore.RequestThrottling.RequestThrottlingOptions> options) { } + [System.Diagnostics.DebuggerStepThroughAttribute] + public System.Threading.Tasks.Task Invoke(Microsoft.AspNetCore.Http.HttpContext context) { throw null; } + } + public partial class RequestThrottlingOptions + { + public RequestThrottlingOptions() { } + public int? MaxConcurrentRequests { [System.Runtime.CompilerServices.CompilerGeneratedAttribute]get { throw null; } [System.Runtime.CompilerServices.CompilerGeneratedAttribute]set { } } + } +} diff --git a/src/Middleware/RequestThrottling/sample/RequestThrottlingSample.csproj b/src/Middleware/RequestThrottling/sample/RequestThrottlingSample.csproj index 0f80e6516ad..9f49f115c0e 100644 --- a/src/Middleware/RequestThrottling/sample/RequestThrottlingSample.csproj +++ b/src/Middleware/RequestThrottling/sample/RequestThrottlingSample.csproj @@ -1,13 +1,13 @@ -<Project Sdk="Microsoft.NET.Sdk.Web"> +<Project Sdk="Microsoft.NET.Sdk.Web"> <PropertyGroup> <TargetFramework>netcoreapp3.0</TargetFramework> </PropertyGroup> <ItemGroup> - <Reference Include="Microsoft.AspNetCore.Server.Kestrel" /> <Reference Include="Microsoft.Extensions.Logging.Console" /> <Reference Include="Microsoft.AspNetCore.RequestThrottling" /> + <Reference Include="Microsoft.AspNetCore.Server.Kestrel" /> </ItemGroup> </Project> diff --git a/src/Middleware/RequestThrottling/sample/Startup.cs b/src/Middleware/RequestThrottling/sample/Startup.cs index 95a94be56d6..113f48d8604 100644 --- a/src/Middleware/RequestThrottling/sample/Startup.cs +++ b/src/Middleware/RequestThrottling/sample/Startup.cs @@ -1,12 +1,12 @@ -using System; -using System.Collections.Generic; +// 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; -using System.Linq; -using System.Net; using System.Threading.Tasks; using Microsoft.AspNetCore.Builder; using Microsoft.AspNetCore.Hosting; using Microsoft.AspNetCore.Http; +using Microsoft.AspNetCore.RequestThrottling; using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.Hosting; using Microsoft.Extensions.Logging; @@ -19,13 +19,20 @@ namespace RequestThrottlingSample // For more information on how to configure your application, visit https://go.microsoft.com/fwlink/?LinkID=398940 public void ConfigureServices(IServiceCollection services) { + services.Configure<RequestThrottlingOptions>(options => + { + options.MaxConcurrentRequests = 2; + }); } - public void Configure(IApplicationBuilder app, IWebHostEnvironment environment) + public void Configure(IApplicationBuilder app, ILoggerFactory loggerFactory) { + app.UseRequestThrottling(); + app.Run(async context => { - await context.Response.WriteAsync("Hello world!"); + await context.Response.WriteAsync("Hello Request Throttling! <p></p>"); + await Task.Delay(1000); }); } diff --git a/src/Middleware/RequestThrottling/src/Internal/RequestQueue.cs b/src/Middleware/RequestThrottling/src/Internal/RequestQueue.cs new file mode 100644 index 00000000000..a09ddeb79dd --- /dev/null +++ b/src/Middleware/RequestThrottling/src/Internal/RequestQueue.cs @@ -0,0 +1,64 @@ +// 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.Threading; +using System.Threading.Tasks; + +namespace Microsoft.AspNetCore.RequestThrottling.Internal +{ + internal class RequestQueue : IDisposable + { + private SemaphoreSlim _semaphore; + private object _waitingRequestsLock = new object(); + public readonly int MaxConcurrentRequests; + public int WaitingRequests { get; private set; } + + public RequestQueue(int maxConcurrentRequests) + { + MaxConcurrentRequests = maxConcurrentRequests; + _semaphore = new SemaphoreSlim(maxConcurrentRequests); + } + + public async Task EnterQueue() + { + var waitInQueueTask = _semaphore.WaitAsync(); + + var needsToWaitOnQueue = !waitInQueueTask.IsCompletedSuccessfully; + if (needsToWaitOnQueue) + { + lock (_waitingRequestsLock) + { + WaitingRequests++; + } + + await waitInQueueTask; + + lock (_waitingRequestsLock) + { + WaitingRequests--; + } + } + } + + public void Release() + { + _semaphore.Release(); + } + + public int Count + { + get => _semaphore.CurrentCount; + } + + public int ConcurrentRequests + { + get => MaxConcurrentRequests - _semaphore.CurrentCount; + } + + public void Dispose() + { + _semaphore.Dispose(); + } + } +} diff --git a/src/Middleware/RequestThrottling/src/Microsoft.AspNetCore.RequestThrottling.csproj b/src/Middleware/RequestThrottling/src/Microsoft.AspNetCore.RequestThrottling.csproj index 5014e9cec59..0090f373c0f 100644 --- a/src/Middleware/RequestThrottling/src/Microsoft.AspNetCore.RequestThrottling.csproj +++ b/src/Middleware/RequestThrottling/src/Microsoft.AspNetCore.RequestThrottling.csproj @@ -1,4 +1,4 @@ -<Project Sdk="Microsoft.NET.Sdk"> +<Project Sdk="Microsoft.NET.Sdk"> <PropertyGroup> <Description>ASP.NET Core middleware for queuing incoming HTTP requests, to avoid threadpool starvation.</Description> @@ -7,4 +7,10 @@ <PackageTags>aspnetcore;queue;queuing</PackageTags> </PropertyGroup> + <ItemGroup> + <Reference Include="Microsoft.AspNetCore.Http.Abstractions" /> + <Reference Include="Microsoft.Extensions.Logging.Abstractions" /> + <Reference Include="Microsoft.Extensions.Options" /> + </ItemGroup> + </Project> diff --git a/src/Middleware/RequestThrottling/src/RequestThrottlingExtensions.cs b/src/Middleware/RequestThrottling/src/RequestThrottlingExtensions.cs new file mode 100644 index 00000000000..17968b44d59 --- /dev/null +++ b/src/Middleware/RequestThrottling/src/RequestThrottlingExtensions.cs @@ -0,0 +1,29 @@ +// 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 Microsoft.AspNetCore.RequestThrottling; + +namespace Microsoft.AspNetCore.Builder +{ + /// <summary> + /// Extension methods for adding the <see cref="RequestThrottlingMiddleware"/> to an application. + /// </summary> + public static class RequestThrottlingExtensions + { + /// <summary> + /// Adds the <see cref="RequestThrottlingMiddleware"/> to limit the number of concurrently-executing requests. + /// </summary> + /// <param name="app">The <see cref="IApplicationBuilder"/>.</param> + /// <returns>The <see cref="IApplicationBuilder"/>.</returns> + public static IApplicationBuilder UseRequestThrottling(this IApplicationBuilder app) + { + if (app == null) + { + throw new ArgumentNullException(nameof(app)); + } + + return app.UseMiddleware<RequestThrottlingMiddleware>(); + } + } +} diff --git a/src/Middleware/RequestThrottling/src/RequestThrottlingMiddleware.cs b/src/Middleware/RequestThrottling/src/RequestThrottlingMiddleware.cs new file mode 100644 index 00000000000..9eaec8cfbc3 --- /dev/null +++ b/src/Middleware/RequestThrottling/src/RequestThrottlingMiddleware.cs @@ -0,0 +1,116 @@ +// 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.Threading.Tasks; +using Microsoft.AspNetCore.Http; +using Microsoft.AspNetCore.RequestThrottling.Internal; +using Microsoft.Extensions.Logging; +using Microsoft.Extensions.Options; + +namespace Microsoft.AspNetCore.RequestThrottling +{ + /// <summary> + /// Limits the number of concurrent requests allowed in the application. + /// </summary> + public class RequestThrottlingMiddleware + { + private readonly RequestQueue _requestQueue; + private readonly RequestThrottlingOptions _options; + private readonly RequestDelegate _next; + private readonly ILogger _logger; + + /// <summary> + /// Creates a new <see cref="RequestThrottlingMiddleware"/>. + /// </summary> + /// <param name="next">The <see cref="RequestDelegate"/> representing the next middleware in the pipeline.</param> + /// <param name="loggerFactory">The <see cref="ILoggerFactory"/> used for logging.</param> + /// <param name="options">The <see cref="RequestThrottlingOptions"/> containing the initialization parameters.</param> + public RequestThrottlingMiddleware(RequestDelegate next, ILoggerFactory loggerFactory, IOptions<RequestThrottlingOptions> options) + { + if (options.Value.MaxConcurrentRequests == null) + { + throw new ArgumentException("The value of 'options.MaxConcurrentRequests' must be specified.", nameof(options)); + } + + _next = next; + _logger = loggerFactory.CreateLogger<RequestThrottlingMiddleware>(); + _options = options.Value; + _requestQueue = new RequestQueue(_options.MaxConcurrentRequests.Value); + } + + /// <summary> + /// Invokes the logic of the middleware. + /// </summary> + /// <param name="context">The <see cref="HttpContext"/>.</param> + /// <returns>A <see cref="Task"/> that completes when the request leaves.</returns> + public async Task Invoke(HttpContext context) + { + var waitInQueueTask = _requestQueue.EnterQueue(); + + if (waitInQueueTask.IsCompletedSuccessfully) + { + RequestThrottlingLog.RequestRunImmediately(_logger); + } + else + { + RequestThrottlingLog.RequestEnqueued(_logger, WaitingRequests); + await waitInQueueTask; + RequestThrottlingLog.RequestDequeued(_logger, WaitingRequests); + } + + try + { + await _next(context); + } + finally + { + _requestQueue.Release(); + } + } + + /// <summary> + /// The number of live requests that are downstream from this middleware. + /// Cannot exceeed <see cref="RequestThrottlingOptions.MaxConcurrentRequests"/>. + /// </summary> + internal int ConcurrentRequests + { + get => _requestQueue.ConcurrentRequests; + } + + /// <summary> + /// Number of requests currently enqueued and waiting to be processed. + /// </summary> + internal int WaitingRequests + { + get => _requestQueue.WaitingRequests; + } + + private static class RequestThrottlingLog + { + private static readonly Action<ILogger, int, Exception> _requestEnqueued = + LoggerMessage.Define<int>(LogLevel.Debug, new EventId(1, "RequestEnqueued"), "Concurrent request limit reached, queuing request. Current queue length: {QueuedRequests}."); + + private static readonly Action<ILogger, int, Exception> _requestDequeued = + LoggerMessage.Define<int>(LogLevel.Debug, new EventId(2, "RequestDequeued"), "Request dequeued. Current queue length: {QueuedRequests}."); + + private static readonly Action<ILogger, Exception> _requestRunImmediately = + LoggerMessage.Define(LogLevel.Debug, new EventId(3, "RequestRunImmediately"), "Concurrent request limit has not been reached, running request immediately."); + + internal static void RequestEnqueued(ILogger logger, int queuedRequests) + { + _requestEnqueued(logger, queuedRequests, null); + } + + internal static void RequestDequeued(ILogger logger, int queuedRequests) + { + _requestDequeued(logger, queuedRequests, null); + } + + internal static void RequestRunImmediately(ILogger logger) + { + _requestRunImmediately(logger, null); + } + } + } +} diff --git a/src/Middleware/RequestThrottling/src/RequestThrottlingOptions.cs b/src/Middleware/RequestThrottling/src/RequestThrottlingOptions.cs new file mode 100644 index 00000000000..64a832640fa --- /dev/null +++ b/src/Middleware/RequestThrottling/src/RequestThrottlingOptions.cs @@ -0,0 +1,19 @@ +// 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 Microsoft.AspNetCore.RequestThrottling; + +namespace Microsoft.AspNetCore.RequestThrottling +{ + /// <summary> + /// Specifies options for the <see cref="RequestThrottlingMiddleware"/>. + /// </summary> + public class RequestThrottlingOptions + { + /// <summary> + /// Maximum number of concurrent requests. Any extras will be queued on the server. + /// This is null by default because the correct value is application specific. This option must be configured by the application. + /// </summary> + public int? MaxConcurrentRequests { get; set; } + } +} diff --git a/src/Middleware/RequestThrottling/src/SemaphoreWrapper.cs b/src/Middleware/RequestThrottling/src/SemaphoreWrapper.cs deleted file mode 100644 index 4c79b947775..00000000000 --- a/src/Middleware/RequestThrottling/src/SemaphoreWrapper.cs +++ /dev/null @@ -1,38 +0,0 @@ -using System; -using System.Collections.Generic; -using System.Text; -using System.Threading; -using System.Threading.Tasks; - -namespace Microsoft.AspNetCore.RequestThrottling -{ - internal class SemaphoreWrapper : IDisposable - { - private SemaphoreSlim _semaphore; - - public SemaphoreWrapper(int queueLength) - { - _semaphore = new SemaphoreSlim(queueLength); - } - - public Task EnterQueue() - { - return _semaphore.WaitAsync(); - } - - public void LeaveQueue() - { - _semaphore.Release(); - } - - public int Count - { - get => _semaphore.CurrentCount; - } - - public void Dispose() - { - _semaphore.Dispose(); - } - } -} diff --git a/src/Middleware/RequestThrottling/test/Microsoft.AspNetCore.RequestThrottling.Tests.csproj b/src/Middleware/RequestThrottling/test/Microsoft.AspNetCore.RequestThrottling.Tests.csproj index 8c0dd8e989a..78b1c886926 100644 --- a/src/Middleware/RequestThrottling/test/Microsoft.AspNetCore.RequestThrottling.Tests.csproj +++ b/src/Middleware/RequestThrottling/test/Microsoft.AspNetCore.RequestThrottling.Tests.csproj @@ -1,10 +1,16 @@ -<Project Sdk="Microsoft.NET.Sdk"> +<Project Sdk="Microsoft.NET.Sdk"> <PropertyGroup> <TargetFramework>netcoreapp3.0</TargetFramework> </PropertyGroup> <ItemGroup> + <Compile Include="$(SharedSourceRoot)SyncPoint\SyncPoint.cs" /> + </ItemGroup> + + <ItemGroup> + <Reference Include="Microsoft.AspNetCore.Http" /> + <Reference Include="Microsoft.AspNetCore.Hosting" /> <Reference Include="Microsoft.AspNetCore.RequestThrottling" /> </ItemGroup> </Project> diff --git a/src/Middleware/RequestThrottling/test/MiddlewareTests.cs b/src/Middleware/RequestThrottling/test/MiddlewareTests.cs new file mode 100644 index 00000000000..8124cddb491 --- /dev/null +++ b/src/Middleware/RequestThrottling/test/MiddlewareTests.cs @@ -0,0 +1,86 @@ +// 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.Threading.Tasks; +using Microsoft.AspNetCore.Http; +using Microsoft.AspNetCore.Internal; +using Xunit; + +namespace Microsoft.AspNetCore.RequestThrottling.Tests +{ + public class MiddlewareTests + { + [Fact] + public async Task RequestsCanEnterIfSpaceAvailible() + { + var middleware = TestUtils.CreateTestMiddleWare(maxConcurrentRequests: 1); + var context = new DefaultHttpContext(); + + // a request should go through with no problems + await middleware.Invoke(context).OrTimeout(); + } + + [Fact] + public async Task SemaphoreStatePreservedIfRequestsError() + { + var middleware = TestUtils.CreateTestMiddleWare( + maxConcurrentRequests: 1, + next: httpContext => + { + throw new DivideByZeroException(); + }); + + Assert.Equal(0, middleware.ConcurrentRequests); + + await Assert.ThrowsAsync<DivideByZeroException>(() => middleware.Invoke(new DefaultHttpContext())); + + Assert.Equal(0, middleware.ConcurrentRequests); + } + + [Fact] + public async Task QueuedRequestsContinueWhenSpaceBecomesAvailible() + { + var blocker = new SyncPoint(); + var firstRequest = true; + + var middleware = TestUtils.CreateTestMiddleWare( + maxConcurrentRequests: 1, + next: httpContext => + { + if (firstRequest) + { + firstRequest = false; + return blocker.WaitToContinue(); + } + return Task.CompletedTask; + }); + + // t1 (as the first request) is blocked by the tcs blocker + var t1 = middleware.Invoke(new DefaultHttpContext()); + Assert.Equal(1, middleware.ConcurrentRequests); + Assert.Equal(0, middleware.WaitingRequests); + + // t2 is blocked from entering the server since t1 already exists there + // note: increasing MaxConcurrentRequests would allow t2 through while t1 is blocked + var t2 = middleware.Invoke(new DefaultHttpContext()); + Assert.Equal(1, middleware.ConcurrentRequests); + Assert.Equal(1, middleware.WaitingRequests); + + // unblock the first task, and the second should follow + blocker.Continue(); + await t1.OrTimeout(); + await t2.OrTimeout(); + } + + [Fact] + public void InvalidArgumentIfMaxConcurrentRequestsIsNull() + { + var ex = Assert.Throws<ArgumentException>(() => + { + TestUtils.CreateTestMiddleWare(maxConcurrentRequests: null); + }); + Assert.Equal("options", ex.ParamName); + } + } +} diff --git a/src/Middleware/RequestThrottling/test/SemaphoreWrapperTests.cs b/src/Middleware/RequestThrottling/test/RequestQueueTests.cs similarity index 60% rename from src/Middleware/RequestThrottling/test/SemaphoreWrapperTests.cs rename to src/Middleware/RequestThrottling/test/RequestQueueTests.cs index b5cdfce18f2..67eeda5d67a 100644 --- a/src/Middleware/RequestThrottling/test/SemaphoreWrapperTests.cs +++ b/src/Middleware/RequestThrottling/test/RequestQueueTests.cs @@ -1,34 +1,48 @@ // 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 Xunit; -using System.Threading; using System.Threading.Tasks; -using System; -using System.Runtime.CompilerServices; -using Microsoft.AspNetCore.Testing; +using Microsoft.AspNetCore.RequestThrottling.Internal; +using Xunit; namespace Microsoft.AspNetCore.RequestThrottling.Tests { - public class SemaphoreWrapperTests + public class RequestQueueTests { [Fact] - public async Task TracksQueueLength() + public async Task LimitsIncomingRequests() { - using var s = new SemaphoreWrapper(1); + using var s = new RequestQueue(1); Assert.Equal(1, s.Count); await s.EnterQueue().OrTimeout(); Assert.Equal(0, s.Count); - s.LeaveQueue(); + s.Release(); Assert.Equal(1, s.Count); } + [Fact] + public async Task TracksQueueLength() + { + using var s = new RequestQueue(1); + Assert.Equal(0, s.WaitingRequests); + + await s.EnterQueue(); + Assert.Equal(0, s.WaitingRequests); + + var enterQueueTask = s.EnterQueue(); + Assert.Equal(1, s.WaitingRequests); + + s.Release(); + await enterQueueTask; + Assert.Equal(0, s.WaitingRequests); + } + [Fact] public void DoesNotWaitIfSpaceAvailible() { - using var s = new SemaphoreWrapper(2); + using var s = new RequestQueue(2); var t1 = s.EnterQueue(); Assert.True(t1.IsCompleted); @@ -43,21 +57,21 @@ namespace Microsoft.AspNetCore.RequestThrottling.Tests [Fact] public async Task WaitsIfNoSpaceAvailible() { - using var s = new SemaphoreWrapper(1); + using var s = new RequestQueue(1); await s.EnterQueue().OrTimeout(); var waitingTask = s.EnterQueue(); Assert.False(waitingTask.IsCompleted); - s.LeaveQueue(); + s.Release(); await waitingTask.OrTimeout(); } [Fact] public async Task IsEncapsulated() { - using var s1 = new SemaphoreWrapper(1); - using var s2 = new SemaphoreWrapper(1); + using var s1 = new RequestQueue(1); + using var s2 = new RequestQueue(1); await s1.EnterQueue().OrTimeout(); await s2.EnterQueue().OrTimeout(); diff --git a/src/Middleware/RequestThrottling/test/TestUtils.cs b/src/Middleware/RequestThrottling/test/TestUtils.cs new file mode 100644 index 00000000000..438d08ab8d8 --- /dev/null +++ b/src/Middleware/RequestThrottling/test/TestUtils.cs @@ -0,0 +1,28 @@ +// 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.Threading.Tasks; +using Microsoft.AspNetCore.RequestThrottling; +using Microsoft.AspNetCore.Http; +using Microsoft.Extensions.Logging.Abstractions; +using Microsoft.Extensions.Options; + +namespace Microsoft.AspNetCore.RequestThrottling.Tests +{ + public static class TestUtils + { + public static RequestThrottlingMiddleware CreateTestMiddleWare(int? maxConcurrentRequests, RequestDelegate next = null) + { + var options = new RequestThrottlingOptions + { + MaxConcurrentRequests = maxConcurrentRequests + }; + + return new RequestThrottlingMiddleware( + next: next ?? (context => Task.CompletedTask), + loggerFactory: NullLoggerFactory.Instance, + options: Options.Create(options) + ); + } + } +} diff --git a/src/Middleware/ResponseCaching/samples/ResponseCachingSample/Startup.cs b/src/Middleware/ResponseCaching/samples/ResponseCachingSample/Startup.cs index ca2e7fbcf3a..6184a36946c 100644 --- a/src/Middleware/ResponseCaching/samples/ResponseCachingSample/Startup.cs +++ b/src/Middleware/ResponseCaching/samples/ResponseCachingSample/Startup.cs @@ -1,4 +1,4 @@ -// Copyright (c) .NET Foundation. All rights reserved. +// 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; diff --git a/src/Middleware/ResponseCaching/src/Microsoft.AspNetCore.ResponseCaching.csproj b/src/Middleware/ResponseCaching/src/Microsoft.AspNetCore.ResponseCaching.csproj index 9611dfbdaf3..ef8199808ca 100644 --- a/src/Middleware/ResponseCaching/src/Microsoft.AspNetCore.ResponseCaching.csproj +++ b/src/Middleware/ResponseCaching/src/Microsoft.AspNetCore.ResponseCaching.csproj @@ -1,4 +1,4 @@ -<Project Sdk="Microsoft.NET.Sdk"> +<Project Sdk="Microsoft.NET.Sdk"> <PropertyGroup> <Description>ASP.NET Core middleware for caching HTTP responses on the server.</Description> diff --git a/src/Servers/IIS/AspNetCoreModuleV2/ref/Microsoft.AspNetCore.ANCMSymbols.csproj b/src/Servers/IIS/AspNetCoreModuleV2/ref/Microsoft.AspNetCore.ANCMSymbols.csproj new file mode 100644 index 00000000000..36c3a47a9af --- /dev/null +++ b/src/Servers/IIS/AspNetCoreModuleV2/ref/Microsoft.AspNetCore.ANCMSymbols.csproj @@ -0,0 +1,10 @@ +<!-- This file is automatically generated. --> +<Project Sdk="Microsoft.NET.Sdk"> + <PropertyGroup> + <TargetFrameworks>netcoreapp3.0</TargetFrameworks> + </PropertyGroup> + <ItemGroup Condition="'$(TargetFramework)' == 'netcoreapp3.0'"> + <Compile Include="Microsoft.AspNetCore.ANCMSymbols.netcoreapp3.0.cs" /> + + </ItemGroup> +</Project> diff --git a/src/Servers/IIS/AspNetCoreModuleV2/ref/Microsoft.AspNetCore.ANCMSymbols.netcoreapp3.0.cs b/src/Servers/IIS/AspNetCoreModuleV2/ref/Microsoft.AspNetCore.ANCMSymbols.netcoreapp3.0.cs new file mode 100644 index 00000000000..618082bc4a8 --- /dev/null +++ b/src/Servers/IIS/AspNetCoreModuleV2/ref/Microsoft.AspNetCore.ANCMSymbols.netcoreapp3.0.cs @@ -0,0 +1,3 @@ +// 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. + diff --git a/src/SignalR/common/testassets/Tests.Utils/SyncPoint.cs b/src/Shared/SyncPoint/SyncPoint.cs similarity index 98% rename from src/SignalR/common/testassets/Tests.Utils/SyncPoint.cs rename to src/Shared/SyncPoint/SyncPoint.cs index 55f4a034d50..ccf36d59dfc 100644 --- a/src/SignalR/common/testassets/Tests.Utils/SyncPoint.cs +++ b/src/Shared/SyncPoint/SyncPoint.cs @@ -4,7 +4,7 @@ using System; using System.Threading.Tasks; -namespace Microsoft.AspNetCore.SignalR.Tests +namespace Microsoft.AspNetCore.Internal { public class SyncPoint { diff --git a/src/SignalR/clients/csharp/Client/test/UnitTests/HttpConnectionTests.ConnectionLifecycle.cs b/src/SignalR/clients/csharp/Client/test/UnitTests/HttpConnectionTests.ConnectionLifecycle.cs index 2088ef39270..61821226b2c 100644 --- a/src/SignalR/clients/csharp/Client/test/UnitTests/HttpConnectionTests.ConnectionLifecycle.cs +++ b/src/SignalR/clients/csharp/Client/test/UnitTests/HttpConnectionTests.ConnectionLifecycle.cs @@ -10,6 +10,7 @@ using Microsoft.AspNetCore.Connections; using Microsoft.AspNetCore.Http.Connections; using Microsoft.AspNetCore.Http.Connections.Client; using Microsoft.AspNetCore.Http.Connections.Client.Internal; +using Microsoft.AspNetCore.Internal; using Microsoft.AspNetCore.SignalR.Tests; using Microsoft.Extensions.Logging; using Microsoft.Extensions.Logging.Testing; @@ -90,8 +91,8 @@ namespace Microsoft.AspNetCore.SignalR.Client.Tests var startCounter = 0; var expected = new Exception("Transport failed to start"); - // We have 4 cases here. Falling back once, falling back twice and each of these - // with WebSockets available and not. If Websockets aren't available and + // We have 4 cases here. Falling back once, falling back twice and each of these + // with WebSockets available and not. If Websockets aren't available and // we can't to test the fallback once scenario we don't decrement the passthreshold // because we still try to start twice (SSE and LP). if (!TestHelpers.IsWebSocketsSupported() && passThreshold > 2) diff --git a/src/SignalR/clients/csharp/Client/test/UnitTests/HubConnectionTests.ConnectionLifecycle.cs b/src/SignalR/clients/csharp/Client/test/UnitTests/HubConnectionTests.ConnectionLifecycle.cs index f48742474bc..647aa433cac 100644 --- a/src/SignalR/clients/csharp/Client/test/UnitTests/HubConnectionTests.ConnectionLifecycle.cs +++ b/src/SignalR/clients/csharp/Client/test/UnitTests/HubConnectionTests.ConnectionLifecycle.cs @@ -8,6 +8,7 @@ using System.Text; using System.Threading; using System.Threading.Tasks; using Microsoft.AspNetCore.Connections; +using Microsoft.AspNetCore.Internal; using Microsoft.AspNetCore.SignalR.Protocol; using Microsoft.AspNetCore.SignalR.Tests; using Microsoft.Extensions.DependencyInjection; diff --git a/src/SignalR/clients/csharp/Client/test/UnitTests/Microsoft.AspNetCore.SignalR.Client.Tests.csproj b/src/SignalR/clients/csharp/Client/test/UnitTests/Microsoft.AspNetCore.SignalR.Client.Tests.csproj index a2b40fbe256..cc45556edd0 100644 --- a/src/SignalR/clients/csharp/Client/test/UnitTests/Microsoft.AspNetCore.SignalR.Client.Tests.csproj +++ b/src/SignalR/clients/csharp/Client/test/UnitTests/Microsoft.AspNetCore.SignalR.Client.Tests.csproj @@ -1,4 +1,4 @@ -<Project Sdk="Microsoft.NET.Sdk"> +<Project Sdk="Microsoft.NET.Sdk"> <PropertyGroup> <TargetFramework>netcoreapp3.0</TargetFramework> @@ -8,6 +8,7 @@ <Compile Include="$(SignalRSharedSourceRoot)MemoryBufferWriter.cs" Link="MemoryBufferWriter.cs" /> <Compile Include="$(SignalRSharedSourceRoot)TextMessageFormatter.cs" Link="TextMessageFormatter.cs" /> <Compile Include="$(SignalRSharedSourceRoot)TextMessageParser.cs" Link="TextMessageParser.cs" /> + <Compile Include="$(SharedSourceRoot)SyncPoint\SyncPoint.cs" /> </ItemGroup> <ItemGroup> diff --git a/src/SignalR/clients/csharp/Client/test/UnitTests/ServerSentEventsTransportTests.cs b/src/SignalR/clients/csharp/Client/test/UnitTests/ServerSentEventsTransportTests.cs index 421e1cef616..aa3c41bbed1 100644 --- a/src/SignalR/clients/csharp/Client/test/UnitTests/ServerSentEventsTransportTests.cs +++ b/src/SignalR/clients/csharp/Client/test/UnitTests/ServerSentEventsTransportTests.cs @@ -11,6 +11,7 @@ using System.Threading; using System.Threading.Tasks; using Microsoft.AspNetCore.Connections; using Microsoft.AspNetCore.Http.Connections.Client.Internal; +using Microsoft.AspNetCore.Internal; using Microsoft.AspNetCore.SignalR.Tests; using Microsoft.Extensions.Logging.Testing; using Moq; diff --git a/src/SignalR/common/Http.Connections/test/HttpConnectionDispatcherTests.cs b/src/SignalR/common/Http.Connections/test/HttpConnectionDispatcherTests.cs index 5b4c164ce38..2fcf129790e 100644 --- a/src/SignalR/common/Http.Connections/test/HttpConnectionDispatcherTests.cs +++ b/src/SignalR/common/Http.Connections/test/HttpConnectionDispatcherTests.cs @@ -21,6 +21,7 @@ using Microsoft.AspNetCore.Http.Connections.Internal; using Microsoft.AspNetCore.Http.Connections.Internal.Transports; using Microsoft.AspNetCore.Http.Features; using Microsoft.AspNetCore.Http.Internal; +using Microsoft.AspNetCore.Internal; using Microsoft.AspNetCore.SignalR.Tests; using Microsoft.AspNetCore.Testing; using Microsoft.AspNetCore.Testing.xunit; diff --git a/src/SignalR/common/Http.Connections/test/Microsoft.AspNetCore.Http.Connections.Tests.csproj b/src/SignalR/common/Http.Connections/test/Microsoft.AspNetCore.Http.Connections.Tests.csproj index 6c03dfe4ebc..1f6b36a8bae 100644 --- a/src/SignalR/common/Http.Connections/test/Microsoft.AspNetCore.Http.Connections.Tests.csproj +++ b/src/SignalR/common/Http.Connections/test/Microsoft.AspNetCore.Http.Connections.Tests.csproj @@ -1,4 +1,4 @@ -<Project Sdk="Microsoft.NET.Sdk"> +<Project Sdk="Microsoft.NET.Sdk"> <PropertyGroup> <TargetFramework>netcoreapp3.0</TargetFramework> @@ -6,6 +6,7 @@ <ItemGroup> <Compile Include="$(SharedSourceRoot)Buffers.Testing\**\*.cs" /> + <Compile Include="$(SharedSourceRoot)SyncPoint\SyncPoint.cs" /> </ItemGroup> <ItemGroup> diff --git a/src/SignalR/server/SignalR/test/Microsoft.AspNetCore.SignalR.Tests.csproj b/src/SignalR/server/SignalR/test/Microsoft.AspNetCore.SignalR.Tests.csproj index c4fd5fd37b3..24958a00b6c 100644 --- a/src/SignalR/server/SignalR/test/Microsoft.AspNetCore.SignalR.Tests.csproj +++ b/src/SignalR/server/SignalR/test/Microsoft.AspNetCore.SignalR.Tests.csproj @@ -1,9 +1,12 @@ -<Project Sdk="Microsoft.NET.Sdk"> +<Project Sdk="Microsoft.NET.Sdk"> <PropertyGroup> <TargetFramework>netcoreapp3.0</TargetFramework> </PropertyGroup> + <ItemGroup> + <Compile Include="$(SharedSourceRoot)SyncPoint\SyncPoint.cs" /> + </ItemGroup> <ItemGroup> <ProjectReference Include="$(SignalRTestUtilsProject)" /> @@ -23,4 +26,4 @@ <Reference Include="System.Reactive.Linq" /> </ItemGroup> -</Project> +</Project> \ No newline at end of file diff --git a/src/SignalR/server/SignalR/test/SerializedHubMessageTests.cs b/src/SignalR/server/SignalR/test/SerializedHubMessageTests.cs index b69952b523b..da55a5e6751 100644 --- a/src/SignalR/server/SignalR/test/SerializedHubMessageTests.cs +++ b/src/SignalR/server/SignalR/test/SerializedHubMessageTests.cs @@ -1,5 +1,6 @@ using System.Linq; using System.Threading.Tasks; +using Microsoft.AspNetCore.Internal; using Microsoft.AspNetCore.SignalR.Protocol; using Xunit; -- GitLab