From 1b2a24eadbf88e2e01a6fc1287be07c45044a6ea Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Tue, 14 Oct 2025 01:49:58 +0000 Subject: [PATCH 1/2] Initial plan From c92cd28eafd1a0f0333ed8ec7271a910428ae319 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Tue, 14 Oct 2025 01:58:27 +0000 Subject: [PATCH 2/2] Delete ConcurrencyLimiter middleware and remove all references Co-authored-by: BrennanConroy <7574801+BrennanConroy@users.noreply.github.com> --- AspNetCore.slnx | 8 - eng/Baseline.Designer.props | 9 - eng/Baseline.xml | 1 - eng/ProjectReferences.props | 1 - eng/ShippingAssemblies.props | 1 - .../ConcurrencyLimiter.slnf | 28 --- .../perf/Microbenchmarks/AssemblyInfo.cs | 4 - ....ConcurrencyLimiter.Microbenchmarks.csproj | 19 -- .../Microbenchmarks/QueueEmptyOverhead.cs | 75 ------ .../perf/Microbenchmarks/QueueFullOverhead.cs | 87 ------- .../QueueRequestsOverwritten.cs | 95 ------- .../sample/ConcurrencyLimiterSample.csproj | 12 - .../sample/Properties/launchSettings.json | 27 -- .../ConcurrencyLimiter/sample/Startup.cs | 42 ---- .../src/ConcurrencyLimiterEventSource.cs | 107 -------- .../src/ConcurrencyLimiterExtensions.cs | 25 -- .../src/ConcurrencyLimiterMiddleware.cs | 100 -------- .../src/ConcurrencyLimiterOptions.cs | 22 -- ...osoft.AspNetCore.ConcurrencyLimiter.csproj | 23 -- .../src/PublicAPI.Shipped.txt | 22 -- .../src/PublicAPI.Unshipped.txt | 1 - .../src/QueuePolicies/BasePolicy.cs | 98 -------- .../src/QueuePolicies/IQueuePolicy.cs | 23 -- .../src/QueuePolicies/QueuePolicy.cs | 15 -- .../src/QueuePolicies/QueuePolicyOptions.cs | 22 -- .../QueuePolicyServiceCollectionExtensions.cs | 40 --- .../src/QueuePolicies/StackPolicy.cs | 15 -- .../ConcurrencyLimiterEventSourceTests.cs | 137 ---------- ...AspNetCore.ConcurrencyLimiter.Tests.csproj | 18 -- .../test/MiddlewareTests.cs | 237 ------------------ .../test/PolicyTests/QueuePolicyTests.cs | 70 ------ .../test/PolicyTests/StackPolicyTests.cs | 151 ----------- .../ConcurrencyLimiter/test/TaskExtensions.cs | 35 --- .../ConcurrencyLimiter/test/TestUtils.cs | 114 --------- src/Middleware/Middleware.slnf | 4 - src/Tools/Tools.slnf | 1 - 36 files changed, 1689 deletions(-) delete mode 100644 src/Middleware/ConcurrencyLimiter/ConcurrencyLimiter.slnf delete mode 100644 src/Middleware/ConcurrencyLimiter/perf/Microbenchmarks/AssemblyInfo.cs delete mode 100644 src/Middleware/ConcurrencyLimiter/perf/Microbenchmarks/Microsoft.AspNetCore.ConcurrencyLimiter.Microbenchmarks.csproj delete mode 100644 src/Middleware/ConcurrencyLimiter/perf/Microbenchmarks/QueueEmptyOverhead.cs delete mode 100644 src/Middleware/ConcurrencyLimiter/perf/Microbenchmarks/QueueFullOverhead.cs delete mode 100644 src/Middleware/ConcurrencyLimiter/perf/Microbenchmarks/QueueRequestsOverwritten.cs delete mode 100644 src/Middleware/ConcurrencyLimiter/sample/ConcurrencyLimiterSample.csproj delete mode 100644 src/Middleware/ConcurrencyLimiter/sample/Properties/launchSettings.json delete mode 100644 src/Middleware/ConcurrencyLimiter/sample/Startup.cs delete mode 100644 src/Middleware/ConcurrencyLimiter/src/ConcurrencyLimiterEventSource.cs delete mode 100644 src/Middleware/ConcurrencyLimiter/src/ConcurrencyLimiterExtensions.cs delete mode 100644 src/Middleware/ConcurrencyLimiter/src/ConcurrencyLimiterMiddleware.cs delete mode 100644 src/Middleware/ConcurrencyLimiter/src/ConcurrencyLimiterOptions.cs delete mode 100644 src/Middleware/ConcurrencyLimiter/src/Microsoft.AspNetCore.ConcurrencyLimiter.csproj delete mode 100644 src/Middleware/ConcurrencyLimiter/src/PublicAPI.Shipped.txt delete mode 100644 src/Middleware/ConcurrencyLimiter/src/PublicAPI.Unshipped.txt delete mode 100644 src/Middleware/ConcurrencyLimiter/src/QueuePolicies/BasePolicy.cs delete mode 100644 src/Middleware/ConcurrencyLimiter/src/QueuePolicies/IQueuePolicy.cs delete mode 100644 src/Middleware/ConcurrencyLimiter/src/QueuePolicies/QueuePolicy.cs delete mode 100644 src/Middleware/ConcurrencyLimiter/src/QueuePolicies/QueuePolicyOptions.cs delete mode 100644 src/Middleware/ConcurrencyLimiter/src/QueuePolicies/QueuePolicyServiceCollectionExtensions.cs delete mode 100644 src/Middleware/ConcurrencyLimiter/src/QueuePolicies/StackPolicy.cs delete mode 100644 src/Middleware/ConcurrencyLimiter/test/ConcurrencyLimiterEventSourceTests.cs delete mode 100644 src/Middleware/ConcurrencyLimiter/test/Microsoft.AspNetCore.ConcurrencyLimiter.Tests.csproj delete mode 100644 src/Middleware/ConcurrencyLimiter/test/MiddlewareTests.cs delete mode 100644 src/Middleware/ConcurrencyLimiter/test/PolicyTests/QueuePolicyTests.cs delete mode 100644 src/Middleware/ConcurrencyLimiter/test/PolicyTests/StackPolicyTests.cs delete mode 100644 src/Middleware/ConcurrencyLimiter/test/TaskExtensions.cs delete mode 100644 src/Middleware/ConcurrencyLimiter/test/TestUtils.cs diff --git a/AspNetCore.slnx b/AspNetCore.slnx index 60f5ca0d74fc..6382f5d163cb 100644 --- a/AspNetCore.slnx +++ b/AspNetCore.slnx @@ -469,14 +469,6 @@ - - - - - - - - diff --git a/eng/Baseline.Designer.props b/eng/Baseline.Designer.props index f566358b9acb..ad74465dc235 100644 --- a/eng/Baseline.Designer.props +++ b/eng/Baseline.Designer.props @@ -256,15 +256,6 @@ - - - 9.0.0 - - - - - - 9.0.0 diff --git a/eng/Baseline.xml b/eng/Baseline.xml index 1b4ce55e8e33..bf3cd9b3ab99 100644 --- a/eng/Baseline.xml +++ b/eng/Baseline.xml @@ -46,7 +46,6 @@ Update this list when preparing for a new patch. - diff --git a/eng/ProjectReferences.props b/eng/ProjectReferences.props index 4dcee4af652b..461c89f299b8 100644 --- a/eng/ProjectReferences.props +++ b/eng/ProjectReferences.props @@ -77,7 +77,6 @@ - diff --git a/eng/ShippingAssemblies.props b/eng/ShippingAssemblies.props index 0acec8474d50..6b969841ea81 100644 --- a/eng/ShippingAssemblies.props +++ b/eng/ShippingAssemblies.props @@ -132,7 +132,6 @@ - diff --git a/src/Middleware/ConcurrencyLimiter/ConcurrencyLimiter.slnf b/src/Middleware/ConcurrencyLimiter/ConcurrencyLimiter.slnf deleted file mode 100644 index 7d62ecbf1aea..000000000000 --- a/src/Middleware/ConcurrencyLimiter/ConcurrencyLimiter.slnf +++ /dev/null @@ -1,28 +0,0 @@ -{ - "solution": { - "path": "..\\..\\..\\AspNetCore.slnx", - "projects": [ - "src\\Hosting\\Abstractions\\src\\Microsoft.AspNetCore.Hosting.Abstractions.csproj", - "src\\Hosting\\Hosting\\src\\Microsoft.AspNetCore.Hosting.csproj", - "src\\Hosting\\Server.Abstractions\\src\\Microsoft.AspNetCore.Hosting.Server.Abstractions.csproj", - "src\\Http\\Http.Abstractions\\src\\Microsoft.AspNetCore.Http.Abstractions.csproj", - "src\\Http\\Http.Extensions\\src\\Microsoft.AspNetCore.Http.Extensions.csproj", - "src\\Extensions\\Features\\src\\Microsoft.Extensions.Features.csproj", - "src\\Http\\Http.Features\\src\\Microsoft.AspNetCore.Http.Features.csproj", - "src\\Http\\WebUtilities\\src\\Microsoft.AspNetCore.WebUtilities.csproj", - "src\\Servers\\Connections.Abstractions\\src\\Microsoft.AspNetCore.Connections.Abstractions.csproj", - "src\\Servers\\Kestrel\\Core\\src\\Microsoft.AspNetCore.Server.Kestrel.Core.csproj", - "src\\Servers\\Kestrel\\Kestrel\\src\\Microsoft.AspNetCore.Server.Kestrel.csproj", - "src\\Servers\\Kestrel\\Transport.NamedPipes\\src\\Microsoft.AspNetCore.Server.Kestrel.Transport.NamedPipes.csproj", - "src\\Servers\\Kestrel\\Transport.Quic\\src\\Microsoft.AspNetCore.Server.Kestrel.Transport.Quic.csproj", - "src\\Servers\\Kestrel\\Transport.Sockets\\src\\Microsoft.AspNetCore.Server.Kestrel.Transport.Sockets.csproj", - "src\\Http\\Headers\\src\\Microsoft.Net.Http.Headers.csproj", - "src\\Http\\Http\\src\\Microsoft.AspNetCore.Http.csproj", - "src\\Middleware\\ConcurrencyLimiter\\perf\\Microbenchmarks\\Microsoft.AspNetCore.ConcurrencyLimiter.Microbenchmarks.csproj", - "src\\Middleware\\ConcurrencyLimiter\\sample\\ConcurrencyLimiterSample.csproj", - "src\\Middleware\\ConcurrencyLimiter\\src\\Microsoft.AspNetCore.ConcurrencyLimiter.csproj", - "src\\Middleware\\ConcurrencyLimiter\\test\\Microsoft.AspNetCore.ConcurrencyLimiter.Tests.csproj", - "src\\Middleware\\HttpsPolicy\\src\\Microsoft.AspNetCore.HttpsPolicy.csproj" - ] - } -} diff --git a/src/Middleware/ConcurrencyLimiter/perf/Microbenchmarks/AssemblyInfo.cs b/src/Middleware/ConcurrencyLimiter/perf/Microbenchmarks/AssemblyInfo.cs deleted file mode 100644 index 09f49228e9e6..000000000000 --- a/src/Middleware/ConcurrencyLimiter/perf/Microbenchmarks/AssemblyInfo.cs +++ /dev/null @@ -1,4 +0,0 @@ -// Licensed to the .NET Foundation under one or more agreements. -// The .NET Foundation licenses this file to you under the MIT license. - -[assembly: BenchmarkDotNet.Attributes.AspNetCoreBenchmark] diff --git a/src/Middleware/ConcurrencyLimiter/perf/Microbenchmarks/Microsoft.AspNetCore.ConcurrencyLimiter.Microbenchmarks.csproj b/src/Middleware/ConcurrencyLimiter/perf/Microbenchmarks/Microsoft.AspNetCore.ConcurrencyLimiter.Microbenchmarks.csproj deleted file mode 100644 index 2674be405d14..000000000000 --- a/src/Middleware/ConcurrencyLimiter/perf/Microbenchmarks/Microsoft.AspNetCore.ConcurrencyLimiter.Microbenchmarks.csproj +++ /dev/null @@ -1,19 +0,0 @@ - - - - Exe - $(DefaultNetCoreTargetFramework) - - - - - - - - - - - - - - diff --git a/src/Middleware/ConcurrencyLimiter/perf/Microbenchmarks/QueueEmptyOverhead.cs b/src/Middleware/ConcurrencyLimiter/perf/Microbenchmarks/QueueEmptyOverhead.cs deleted file mode 100644 index 1ab18f60558a..000000000000 --- a/src/Middleware/ConcurrencyLimiter/perf/Microbenchmarks/QueueEmptyOverhead.cs +++ /dev/null @@ -1,75 +0,0 @@ -// Licensed to the .NET Foundation under one or more agreements. -// The .NET Foundation licenses this file to you under the MIT license. - -using BenchmarkDotNet.Attributes; -using Microsoft.AspNetCore.ConcurrencyLimiter.Tests; -using Microsoft.AspNetCore.Http; - -namespace Microsoft.AspNetCore.ConcurrencyLimiter.Microbenchmarks; - -public class QueueEmptyOverhead -{ - private const int _numRequests = 20000; - -#pragma warning disable CS0618 // Type or member is obsolete - private ConcurrencyLimiterMiddleware _middlewareQueue; - private ConcurrencyLimiterMiddleware _middlewareStack; -#pragma warning restore CS0618 // Type or member is obsolete - private RequestDelegate _restOfServer; - - [GlobalSetup] - public void GlobalSetup() - { - _restOfServer = YieldsThreadInternally ? (RequestDelegate)YieldsThread : (RequestDelegate)CompletesImmediately; - - _middlewareQueue = TestUtils.CreateTestMiddleware_QueuePolicy( - maxConcurrentRequests: 1, - requestQueueLimit: 100, - next: _restOfServer); - - _middlewareStack = TestUtils.CreateTestMiddleware_StackPolicy( - maxConcurrentRequests: 1, - requestQueueLimit: 100, - next: _restOfServer); - } - - [Params(false, true)] - public bool YieldsThreadInternally; - - [Benchmark(OperationsPerInvoke = _numRequests)] - public async Task Baseline() - { - for (int i = 0; i < _numRequests; i++) - { - await _restOfServer(null); - } - } - - [Benchmark(OperationsPerInvoke = _numRequests)] - public async Task WithEmptyQueueOverhead_QueuePolicy() - { - for (int i = 0; i < _numRequests; i++) - { - await _middlewareQueue.Invoke(null); - } - } - - [Benchmark(OperationsPerInvoke = _numRequests)] - public async Task WithEmptyQueueOverhead_StackPolicy() - { - for (int i = 0; i < _numRequests; i++) - { - await _middlewareStack.Invoke(null); - } - } - - private static async Task YieldsThread(HttpContext context) - { - await Task.Yield(); - } - - private static Task CompletesImmediately(HttpContext context) - { - return Task.CompletedTask; - } -} diff --git a/src/Middleware/ConcurrencyLimiter/perf/Microbenchmarks/QueueFullOverhead.cs b/src/Middleware/ConcurrencyLimiter/perf/Microbenchmarks/QueueFullOverhead.cs deleted file mode 100644 index 22afdb9bf538..000000000000 --- a/src/Middleware/ConcurrencyLimiter/perf/Microbenchmarks/QueueFullOverhead.cs +++ /dev/null @@ -1,87 +0,0 @@ -// Licensed to the .NET Foundation under one or more agreements. -// The .NET Foundation licenses this file to you under the MIT license. - -using BenchmarkDotNet.Attributes; -using Microsoft.AspNetCore.ConcurrencyLimiter.Tests; -using Microsoft.AspNetCore.Http; - -namespace Microsoft.AspNetCore.ConcurrencyLimiter.Microbenchmarks; - -public class QueueFullOverhead -{ - private const int _numRequests = 2000; - private int _requestCount = 0; - private readonly ManualResetEventSlim _mres = new ManualResetEventSlim(); - -#pragma warning disable CS0618 // Type or member is obsolete - private ConcurrencyLimiterMiddleware _middlewareQueue; - private ConcurrencyLimiterMiddleware _middlewareStack; -#pragma warning restore CS0618 // Type or member is obsolete - - [Params(8)] - public int MaxConcurrentRequests; - - [GlobalSetup] - public void GlobalSetup() - { - _middlewareQueue = TestUtils.CreateTestMiddleware_QueuePolicy( - maxConcurrentRequests: MaxConcurrentRequests, - requestQueueLimit: _numRequests, - next: IncrementAndCheck); - - _middlewareStack = TestUtils.CreateTestMiddleware_StackPolicy( - maxConcurrentRequests: MaxConcurrentRequests, - requestQueueLimit: _numRequests, - next: IncrementAndCheck); - } - - [IterationSetup] - public void Setup() - { - _requestCount = 0; - _mres.Reset(); - } - - private async Task IncrementAndCheck(HttpContext context) - { - if (Interlocked.Increment(ref _requestCount) == _numRequests) - { - _mres.Set(); - } - - await Task.Yield(); - } - - [Benchmark(OperationsPerInvoke = _numRequests)] - public void Baseline() - { - for (int i = 0; i < _numRequests; i++) - { - _ = IncrementAndCheck(null); - } - - _mres.Wait(); - } - - [Benchmark(OperationsPerInvoke = _numRequests)] - public void QueueingAll_QueuePolicy() - { - for (int i = 0; i < _numRequests; i++) - { - _ = _middlewareStack.Invoke(null); - } - - _mres.Wait(); - } - - [Benchmark(OperationsPerInvoke = _numRequests)] - public void QueueingAll_StackPolicy() - { - for (int i = 0; i < _numRequests; i++) - { - _ = _middlewareQueue.Invoke(null); - } - - _mres.Wait(); - } -} diff --git a/src/Middleware/ConcurrencyLimiter/perf/Microbenchmarks/QueueRequestsOverwritten.cs b/src/Middleware/ConcurrencyLimiter/perf/Microbenchmarks/QueueRequestsOverwritten.cs deleted file mode 100644 index 3954ce92e512..000000000000 --- a/src/Middleware/ConcurrencyLimiter/perf/Microbenchmarks/QueueRequestsOverwritten.cs +++ /dev/null @@ -1,95 +0,0 @@ -// Licensed to the .NET Foundation under one or more agreements. -// The .NET Foundation licenses this file to you under the MIT license. - -using BenchmarkDotNet.Attributes; -using Microsoft.AspNetCore.ConcurrencyLimiter.Tests; -using Microsoft.AspNetCore.Http; - -namespace Microsoft.AspNetCore.ConcurrencyLimiter.Microbenchmarks; - -public class QueueRequestsOverwritten -{ - private const int _numRejects = 5000; - private readonly int _queueLength = 20; - private int _rejectionCount = 0; - private readonly ManualResetEventSlim _mres = new ManualResetEventSlim(); - -#pragma warning disable CS0618 // Type or member is obsolete - private ConcurrencyLimiterMiddleware _middlewareQueue; - private ConcurrencyLimiterMiddleware _middlewareStack; -#pragma warning restore CS0618 // Type or member is obsolete - - [GlobalSetup] - public void GlobalSetup() - { - _middlewareQueue = TestUtils.CreateTestMiddleware_QueuePolicy( - maxConcurrentRequests: 1, - requestQueueLimit: 20, - next: WaitForever, - onRejected: IncrementRejections); - - _middlewareStack = TestUtils.CreateTestMiddleware_StackPolicy( - maxConcurrentRequests: 1, - requestQueueLimit: 20, - next: WaitForever, - onRejected: IncrementRejections); - } - - [IterationSetup] - public void Setup() - { - _rejectionCount = 0; - _mres.Reset(); - } - - private async Task IncrementRejections(HttpContext context) - { - if (Interlocked.Increment(ref _rejectionCount) == _numRejects) - { - _mres.Set(); - } - - await Task.Yield(); - } - - private async Task WaitForever(HttpContext context) - { - await Task.Delay(int.MaxValue); - } - - [Benchmark(OperationsPerInvoke = _numRejects)] - public void Baseline() - { - var toSend = _queueLength + _numRejects + 1; - for (int i = 0; i < toSend; i++) - { - _ = IncrementRejections(new DefaultHttpContext()); - } - - _mres.Wait(); - } - - [Benchmark(OperationsPerInvoke = _numRejects)] - public void RejectingRapidly_QueuePolicy() - { - var toSend = _queueLength + _numRejects + 1; - for (int i = 0; i < toSend; i++) - { - _ = _middlewareQueue.Invoke(new DefaultHttpContext()); - } - - _mres.Wait(); - } - - [Benchmark(OperationsPerInvoke = _numRejects)] - public void RejectingRapidly_StackPolicy() - { - var toSend = _queueLength + _numRejects + 1; - for (int i = 0; i < toSend; i++) - { - _ = _middlewareStack.Invoke(new DefaultHttpContext()); - } - - _mres.Wait(); - } -} diff --git a/src/Middleware/ConcurrencyLimiter/sample/ConcurrencyLimiterSample.csproj b/src/Middleware/ConcurrencyLimiter/sample/ConcurrencyLimiterSample.csproj deleted file mode 100644 index a7f5b4921686..000000000000 --- a/src/Middleware/ConcurrencyLimiter/sample/ConcurrencyLimiterSample.csproj +++ /dev/null @@ -1,12 +0,0 @@ - - - $(DefaultNetCoreTargetFramework) - - - - - - - - - diff --git a/src/Middleware/ConcurrencyLimiter/sample/Properties/launchSettings.json b/src/Middleware/ConcurrencyLimiter/sample/Properties/launchSettings.json deleted file mode 100644 index 4e765db3c30f..000000000000 --- a/src/Middleware/ConcurrencyLimiter/sample/Properties/launchSettings.json +++ /dev/null @@ -1,27 +0,0 @@ -{ - "iisSettings": { - "windowsAuthentication": false, - "anonymousAuthentication": true, - "iisExpress": { - "applicationUrl": "http://localhost:54272/", - "sslPort": 44373 - } - }, - "profiles": { - "ConcurrencyLimiterSample": { - "commandName": "Project", - "launchBrowser": true, - "environmentVariables": { - "ASPNETCORE_ENVIRONMENT": "Development" - }, - "applicationUrl": "https://localhost:5001;http://localhost:5000" - }, - "IIS Express": { - "commandName": "IISExpress", - "launchBrowser": true, - "environmentVariables": { - "ASPNETCORE_ENVIRONMENT": "Development" - } - } - } -} diff --git a/src/Middleware/ConcurrencyLimiter/sample/Startup.cs b/src/Middleware/ConcurrencyLimiter/sample/Startup.cs deleted file mode 100644 index 636311b2013a..000000000000 --- a/src/Middleware/ConcurrencyLimiter/sample/Startup.cs +++ /dev/null @@ -1,42 +0,0 @@ -// Licensed to the .NET Foundation under one or more agreements. -// The .NET Foundation licenses this file to you under the MIT license. - -namespace ConcurrencyLimiterSample; - -public class Startup -{ - public void ConfigureServices(IServiceCollection services) - { - services.AddStackPolicy(options => - { - options.MaxConcurrentRequests = 2; - options.RequestQueueLimit = 25; - }); - } - - public void Configure(IApplicationBuilder app, ILoggerFactory loggerFactory) - { -#pragma warning disable CS0618 // Type or member is obsolete - app.UseConcurrencyLimiter(); -#pragma warning restore CS0618 // Type or member is obsolete - app.Run(async context => - { - Task.Delay(100).Wait(); // 100ms sync-over-async - - await context.Response.WriteAsync("Hello World!"); - }); - } - - public static Task Main(string[] args) - { - return new HostBuilder() - .ConfigureWebHost(webHostBuilder => - { - webHostBuilder - .UseKestrel() - .UseStartup(); - }) - .Build() - .RunAsync(); - } -} diff --git a/src/Middleware/ConcurrencyLimiter/src/ConcurrencyLimiterEventSource.cs b/src/Middleware/ConcurrencyLimiter/src/ConcurrencyLimiterEventSource.cs deleted file mode 100644 index 0cc1c3c7c4a3..000000000000 --- a/src/Middleware/ConcurrencyLimiter/src/ConcurrencyLimiterEventSource.cs +++ /dev/null @@ -1,107 +0,0 @@ -// Licensed to the .NET Foundation under one or more agreements. -// The .NET Foundation licenses this file to you under the MIT license. - -using System.Diagnostics.Tracing; -using Microsoft.Extensions.Internal; - -namespace Microsoft.AspNetCore.ConcurrencyLimiter; - -internal sealed class ConcurrencyLimiterEventSource : EventSource -{ - public static readonly ConcurrencyLimiterEventSource Log = new ConcurrencyLimiterEventSource(); - private static readonly QueueFrame CachedNonTimerResult = new QueueFrame(timer: null, parent: Log); - -#pragma warning disable IDE0052 // Remove unread private members (2021-02-02: These ARE set in OnEventCommand - the the IDE0052 analyzer is incorrect at this time) - private PollingCounter? _rejectedRequestsCounter; - private PollingCounter? _queueLengthCounter; -#pragma warning restore IDE0052 // Remove unread private members - - private EventCounter? _queueDuration; - - private long _rejectedRequests; - private int _queueLength; - - internal ConcurrencyLimiterEventSource() - : base("Microsoft.AspNetCore.ConcurrencyLimiter") - { - } - - // Used for testing - internal ConcurrencyLimiterEventSource(string eventSourceName) - : base(eventSourceName, EventSourceSettings.EtwManifestEventFormat) - { - } - - [Event(1, Level = EventLevel.Warning)] - public void RequestRejected() - { - Interlocked.Increment(ref _rejectedRequests); - WriteEvent(1); - } - - [NonEvent] - public void QueueSkipped() - { - if (IsEnabled()) - { - _queueDuration!.WriteMetric(0); - } - } - - [NonEvent] - public QueueFrame QueueTimer() - { - Interlocked.Increment(ref _queueLength); - - if (IsEnabled()) - { - return new QueueFrame(ValueStopwatch.StartNew(), this); - } - - return CachedNonTimerResult; - } - - internal struct QueueFrame : IDisposable - { - private readonly ValueStopwatch? _timer; - private readonly ConcurrencyLimiterEventSource _parent; - - public QueueFrame(ValueStopwatch? timer, ConcurrencyLimiterEventSource parent) - { - _timer = timer; - _parent = parent; - } - - public void Dispose() - { - Interlocked.Decrement(ref _parent._queueLength); - - if (_parent.IsEnabled() && _timer != null) - { - var duration = _timer.Value.GetElapsedTime().TotalMilliseconds; - _parent._queueDuration!.WriteMetric(duration); - } - } - } - - protected override void OnEventCommand(EventCommandEventArgs command) - { - if (command.Command == EventCommand.Enable) - { - _rejectedRequestsCounter ??= new PollingCounter("requests-rejected", this, () => Volatile.Read(ref _rejectedRequests)) - { - DisplayName = "Rejected Requests", - }; - - _queueLengthCounter ??= new PollingCounter("queue-length", this, () => Volatile.Read(ref _queueLength)) - { - DisplayName = "Queue Length", - }; - - _queueDuration ??= new EventCounter("queue-duration", this) - { - DisplayName = "Average Time in Queue", - }; - } - } -} diff --git a/src/Middleware/ConcurrencyLimiter/src/ConcurrencyLimiterExtensions.cs b/src/Middleware/ConcurrencyLimiter/src/ConcurrencyLimiterExtensions.cs deleted file mode 100644 index 2f2ecda96e9c..000000000000 --- a/src/Middleware/ConcurrencyLimiter/src/ConcurrencyLimiterExtensions.cs +++ /dev/null @@ -1,25 +0,0 @@ -// Licensed to the .NET Foundation under one or more agreements. -// The .NET Foundation licenses this file to you under the MIT license. - -using Microsoft.AspNetCore.ConcurrencyLimiter; - -namespace Microsoft.AspNetCore.Builder; - -/// -/// Extension methods for adding the to an application. -/// -public static class ConcurrencyLimiterExtensions -{ - /// - /// Adds the to limit the number of concurrently-executing requests. - /// - /// The . - /// The . - [Obsolete("Concurrency Limiter middleware has been deprecated and will be removed in a future release. Update the app to use concurrency features in rate limiting middleware. For more information, see https://aka.ms/aspnet/rate-limiting")] - public static IApplicationBuilder UseConcurrencyLimiter(this IApplicationBuilder app) - { - ArgumentNullException.ThrowIfNull(app); - - return app.UseMiddleware(); - } -} diff --git a/src/Middleware/ConcurrencyLimiter/src/ConcurrencyLimiterMiddleware.cs b/src/Middleware/ConcurrencyLimiter/src/ConcurrencyLimiterMiddleware.cs deleted file mode 100644 index 8caf22811472..000000000000 --- a/src/Middleware/ConcurrencyLimiter/src/ConcurrencyLimiterMiddleware.cs +++ /dev/null @@ -1,100 +0,0 @@ -// Licensed to the .NET Foundation under one or more agreements. -// The .NET Foundation licenses this file to you under the MIT license. - -using Microsoft.AspNetCore.Http; -using Microsoft.Extensions.Logging; -using Microsoft.Extensions.Options; - -namespace Microsoft.AspNetCore.ConcurrencyLimiter; - -/// -/// Limits the number of concurrent requests allowed in the application. -/// -[Obsolete("Concurrency Limiter middleware has been deprecated and will be removed in a future release. Update the app to use concurrency features in rate limiting middleware. For more information, see https://aka.ms/aspnet/rate-limiting")] -public partial class ConcurrencyLimiterMiddleware -{ - private readonly IQueuePolicy _queuePolicy; - private readonly RequestDelegate _next; - private readonly RequestDelegate _onRejected; - private readonly ILogger _logger; - - /// - /// Creates a new . - /// - /// The representing the next middleware in the pipeline. - /// The used for logging. - /// The queueing strategy to use for the server. - /// The options for the middleware, currently containing the 'OnRejected' callback. - public ConcurrencyLimiterMiddleware(RequestDelegate next, ILoggerFactory loggerFactory, IQueuePolicy queue, IOptions options) - { - if (options.Value.OnRejected == null) - { - throw new ArgumentException("The value of 'options.OnRejected' must not be null.", nameof(options)); - } - - _next = next; - _logger = loggerFactory.CreateLogger(); - _onRejected = options.Value.OnRejected; - _queuePolicy = queue; - } - - /// - /// Invokes the logic of the middleware. - /// - /// The . - /// A that completes when the request leaves. - public async Task Invoke(HttpContext context) - { - var waitInQueueTask = _queuePolicy.TryEnterAsync(); - - // Make sure we only ever call GetResult once on the TryEnterAsync ValueTask b/c it resets. - bool result; - - if (waitInQueueTask.IsCompleted) - { - ConcurrencyLimiterEventSource.Log.QueueSkipped(); - result = waitInQueueTask.Result; - } - else - { - using (ConcurrencyLimiterEventSource.Log.QueueTimer()) - { - result = await waitInQueueTask; - } - } - - if (result) - { - try - { - await _next(context); - } - finally - { - _queuePolicy.OnExit(); - } - } - else - { - ConcurrencyLimiterEventSource.Log.RequestRejected(); - ConcurrencyLimiterLog.RequestRejectedQueueFull(_logger); - context.Response.StatusCode = StatusCodes.Status503ServiceUnavailable; - await _onRejected(context); - } - } - - private static partial class ConcurrencyLimiterLog - { - [LoggerMessage(1, LogLevel.Debug, "MaxConcurrentRequests limit reached, request has been queued. Current active requests: {ActiveRequests}.", EventName = "RequestEnqueued")] - internal static partial void RequestEnqueued(ILogger logger, int activeRequests); - - [LoggerMessage(2, LogLevel.Debug, "Request dequeued. Current active requests: {ActiveRequests}.", EventName = "RequestDequeued")] - internal static partial void RequestDequeued(ILogger logger, int activeRequests); - - [LoggerMessage(3, LogLevel.Debug, "Below MaxConcurrentRequests limit, running request immediately. Current active requests: {ActiveRequests}", EventName = "RequestRunImmediately")] - internal static partial void RequestRunImmediately(ILogger logger, int activeRequests); - - [LoggerMessage(4, LogLevel.Debug, "Currently at the 'RequestQueueLimit', rejecting this request with a '503 server not available' error", EventName = "RequestRejectedQueueFull")] - internal static partial void RequestRejectedQueueFull(ILogger logger); - } -} diff --git a/src/Middleware/ConcurrencyLimiter/src/ConcurrencyLimiterOptions.cs b/src/Middleware/ConcurrencyLimiter/src/ConcurrencyLimiterOptions.cs deleted file mode 100644 index ec71e689211b..000000000000 --- a/src/Middleware/ConcurrencyLimiter/src/ConcurrencyLimiterOptions.cs +++ /dev/null @@ -1,22 +0,0 @@ -// Licensed to the .NET Foundation under one or more agreements. -// The .NET Foundation licenses this file to you under the MIT license. - -using Microsoft.AspNetCore.Http; - -namespace Microsoft.AspNetCore.ConcurrencyLimiter; - -/// -/// Specifies options for the . -/// -[Obsolete("Concurrency Limiter middleware has been deprecated and will be removed in a future release. Update the app to use concurrency features in rate limiting middleware. For more information, see https://aka.ms/aspnet/rate-limiting")] -public class ConcurrencyLimiterOptions -{ - /// - /// A that handles requests rejected by this middleware. - /// If it doesn't modify the response, an empty 503 response will be written. - /// - public RequestDelegate OnRejected { get; set; } = context => - { - return Task.CompletedTask; - }; -} diff --git a/src/Middleware/ConcurrencyLimiter/src/Microsoft.AspNetCore.ConcurrencyLimiter.csproj b/src/Middleware/ConcurrencyLimiter/src/Microsoft.AspNetCore.ConcurrencyLimiter.csproj deleted file mode 100644 index b2936f52a565..000000000000 --- a/src/Middleware/ConcurrencyLimiter/src/Microsoft.AspNetCore.ConcurrencyLimiter.csproj +++ /dev/null @@ -1,23 +0,0 @@ - - - - ASP.NET Core middleware for queuing incoming HTTP requests, to avoid threadpool starvation. - $(DefaultNetCoreTargetFramework) - true - aspnetcore;queue;queuing - enable - - - - - - - - - - - - - - - diff --git a/src/Middleware/ConcurrencyLimiter/src/PublicAPI.Shipped.txt b/src/Middleware/ConcurrencyLimiter/src/PublicAPI.Shipped.txt deleted file mode 100644 index cc188c5a2d4d..000000000000 --- a/src/Middleware/ConcurrencyLimiter/src/PublicAPI.Shipped.txt +++ /dev/null @@ -1,22 +0,0 @@ -#nullable enable -Microsoft.AspNetCore.Builder.ConcurrencyLimiterExtensions -Microsoft.AspNetCore.ConcurrencyLimiter.ConcurrencyLimiterMiddleware -Microsoft.AspNetCore.ConcurrencyLimiter.ConcurrencyLimiterMiddleware.ConcurrencyLimiterMiddleware(Microsoft.AspNetCore.Http.RequestDelegate! next, Microsoft.Extensions.Logging.ILoggerFactory! loggerFactory, Microsoft.AspNetCore.ConcurrencyLimiter.IQueuePolicy! queue, Microsoft.Extensions.Options.IOptions! options) -> void -Microsoft.AspNetCore.ConcurrencyLimiter.ConcurrencyLimiterMiddleware.Invoke(Microsoft.AspNetCore.Http.HttpContext! context) -> System.Threading.Tasks.Task! -Microsoft.AspNetCore.ConcurrencyLimiter.ConcurrencyLimiterOptions -Microsoft.AspNetCore.ConcurrencyLimiter.ConcurrencyLimiterOptions.ConcurrencyLimiterOptions() -> void -Microsoft.AspNetCore.ConcurrencyLimiter.ConcurrencyLimiterOptions.OnRejected.get -> Microsoft.AspNetCore.Http.RequestDelegate! -Microsoft.AspNetCore.ConcurrencyLimiter.ConcurrencyLimiterOptions.OnRejected.set -> void -Microsoft.AspNetCore.ConcurrencyLimiter.IQueuePolicy -Microsoft.AspNetCore.ConcurrencyLimiter.IQueuePolicy.OnExit() -> void -Microsoft.AspNetCore.ConcurrencyLimiter.IQueuePolicy.TryEnterAsync() -> System.Threading.Tasks.ValueTask -Microsoft.AspNetCore.ConcurrencyLimiter.QueuePolicyOptions -Microsoft.AspNetCore.ConcurrencyLimiter.QueuePolicyOptions.MaxConcurrentRequests.get -> int -Microsoft.AspNetCore.ConcurrencyLimiter.QueuePolicyOptions.MaxConcurrentRequests.set -> void -Microsoft.AspNetCore.ConcurrencyLimiter.QueuePolicyOptions.QueuePolicyOptions() -> void -Microsoft.AspNetCore.ConcurrencyLimiter.QueuePolicyOptions.RequestQueueLimit.get -> int -Microsoft.AspNetCore.ConcurrencyLimiter.QueuePolicyOptions.RequestQueueLimit.set -> void -Microsoft.Extensions.DependencyInjection.QueuePolicyServiceCollectionExtensions -static Microsoft.AspNetCore.Builder.ConcurrencyLimiterExtensions.UseConcurrencyLimiter(this Microsoft.AspNetCore.Builder.IApplicationBuilder! app) -> Microsoft.AspNetCore.Builder.IApplicationBuilder! -static Microsoft.Extensions.DependencyInjection.QueuePolicyServiceCollectionExtensions.AddQueuePolicy(this Microsoft.Extensions.DependencyInjection.IServiceCollection! services, System.Action! configure) -> Microsoft.Extensions.DependencyInjection.IServiceCollection! -static Microsoft.Extensions.DependencyInjection.QueuePolicyServiceCollectionExtensions.AddStackPolicy(this Microsoft.Extensions.DependencyInjection.IServiceCollection! services, System.Action! configure) -> Microsoft.Extensions.DependencyInjection.IServiceCollection! diff --git a/src/Middleware/ConcurrencyLimiter/src/PublicAPI.Unshipped.txt b/src/Middleware/ConcurrencyLimiter/src/PublicAPI.Unshipped.txt deleted file mode 100644 index 7dc5c58110bf..000000000000 --- a/src/Middleware/ConcurrencyLimiter/src/PublicAPI.Unshipped.txt +++ /dev/null @@ -1 +0,0 @@ -#nullable enable diff --git a/src/Middleware/ConcurrencyLimiter/src/QueuePolicies/BasePolicy.cs b/src/Middleware/ConcurrencyLimiter/src/QueuePolicies/BasePolicy.cs deleted file mode 100644 index ed3ec46ddd5b..000000000000 --- a/src/Middleware/ConcurrencyLimiter/src/QueuePolicies/BasePolicy.cs +++ /dev/null @@ -1,98 +0,0 @@ -// Licensed to the .NET Foundation under one or more agreements. -// The .NET Foundation licenses this file to you under the MIT license. - -using System.Collections.Concurrent; -using System.Threading.RateLimiting; -using Microsoft.Extensions.Options; -using Limiter = System.Threading.RateLimiting.ConcurrencyLimiter; -using LimiterOptions = System.Threading.RateLimiting.ConcurrencyLimiterOptions; - -namespace Microsoft.AspNetCore.ConcurrencyLimiter; - -internal class BasePolicy : IQueuePolicy, IDisposable -{ - private readonly Limiter _limiter; - private readonly ConcurrentQueue _leases = new ConcurrentQueue(); - - public int TotalRequests => _leases.Count; - - public BasePolicy(IOptions options, QueueProcessingOrder order) - { - var queuePolicyOptions = options.Value; - - var maxConcurrentRequests = queuePolicyOptions.MaxConcurrentRequests; - if (maxConcurrentRequests <= 0) - { - throw new ArgumentException("MaxConcurrentRequests must be a positive integer.", nameof(options)); - } - - var requestQueueLimit = queuePolicyOptions.RequestQueueLimit; - if (requestQueueLimit < 0) - { - throw new ArgumentException("The RequestQueueLimit cannot be a negative number.", nameof(options)); - } - - _limiter = new Limiter(new LimiterOptions - { - PermitLimit = maxConcurrentRequests, - QueueProcessingOrder = order, - QueueLimit = requestQueueLimit - }); - } - - public ValueTask TryEnterAsync() - { - // a return value of 'false' indicates that the request is rejected - // a return value of 'true' indicates that the request may proceed - - var lease = _limiter.AttemptAcquire(); - if (lease.IsAcquired) - { - _leases.Enqueue(lease); - return ValueTask.FromResult(true); - } - - var task = _limiter.AcquireAsync(); - if (task.IsCompletedSuccessfully) - { - lease = task.Result; - if (lease.IsAcquired) - { - _leases.Enqueue(lease); - return ValueTask.FromResult(true); - } - - return ValueTask.FromResult(false); - } - - return Awaited(task); - } - - public void OnExit() - { - if (!_leases.TryDequeue(out var lease)) - { - throw new InvalidOperationException("No outstanding leases."); - } - - lease.Dispose(); - } - - public void Dispose() - { - _limiter.Dispose(); - } - - private async ValueTask Awaited(ValueTask task) - { - var lease = await task; - - if (lease.IsAcquired) - { - _leases.Enqueue(lease); - return true; - } - - return false; - } -} diff --git a/src/Middleware/ConcurrencyLimiter/src/QueuePolicies/IQueuePolicy.cs b/src/Middleware/ConcurrencyLimiter/src/QueuePolicies/IQueuePolicy.cs deleted file mode 100644 index 4ec61663fed2..000000000000 --- a/src/Middleware/ConcurrencyLimiter/src/QueuePolicies/IQueuePolicy.cs +++ /dev/null @@ -1,23 +0,0 @@ -// Licensed to the .NET Foundation under one or more agreements. -// The .NET Foundation licenses this file to you under the MIT license. - -namespace Microsoft.AspNetCore.ConcurrencyLimiter; - -/// -/// Queueing policies, meant to be used with the . -/// -public interface IQueuePolicy -{ - /// - /// Called for every incoming request. - /// When it returns 'true' the request procedes to the server. - /// When it returns 'false' the request is rejected immediately. - /// - ValueTask TryEnterAsync(); - - /// - /// Called after successful requests have been returned from the server. - /// Does NOT get called for rejected requests. - /// - void OnExit(); -} diff --git a/src/Middleware/ConcurrencyLimiter/src/QueuePolicies/QueuePolicy.cs b/src/Middleware/ConcurrencyLimiter/src/QueuePolicies/QueuePolicy.cs deleted file mode 100644 index cea11a8a4f6c..000000000000 --- a/src/Middleware/ConcurrencyLimiter/src/QueuePolicies/QueuePolicy.cs +++ /dev/null @@ -1,15 +0,0 @@ -// Licensed to the .NET Foundation under one or more agreements. -// The .NET Foundation licenses this file to you under the MIT license. - -using System.Threading.RateLimiting; -using Microsoft.Extensions.Options; - -namespace Microsoft.AspNetCore.ConcurrencyLimiter; - -internal sealed class QueuePolicy : BasePolicy -{ - public QueuePolicy(IOptions options) - : base(options, QueueProcessingOrder.OldestFirst) - { - } -} diff --git a/src/Middleware/ConcurrencyLimiter/src/QueuePolicies/QueuePolicyOptions.cs b/src/Middleware/ConcurrencyLimiter/src/QueuePolicies/QueuePolicyOptions.cs deleted file mode 100644 index 42f9add3e34a..000000000000 --- a/src/Middleware/ConcurrencyLimiter/src/QueuePolicies/QueuePolicyOptions.cs +++ /dev/null @@ -1,22 +0,0 @@ -// Licensed to the .NET Foundation under one or more agreements. -// The .NET Foundation licenses this file to you under the MIT license. - -namespace Microsoft.AspNetCore.ConcurrencyLimiter; - -/// -/// Specifies options for the -/// -public class QueuePolicyOptions -{ - /// - /// Maximum number of concurrent requests. Any extras will be queued on the server. - /// This option is highly application dependant, and must be configured by the application. - /// - public int MaxConcurrentRequests { get; set; } - - /// - /// Maximum number of queued requests before the server starts rejecting connections with '503 Service Unavailable'. - /// This option is highly application dependant, and must be configured by the application. - /// - public int RequestQueueLimit { get; set; } -} diff --git a/src/Middleware/ConcurrencyLimiter/src/QueuePolicies/QueuePolicyServiceCollectionExtensions.cs b/src/Middleware/ConcurrencyLimiter/src/QueuePolicies/QueuePolicyServiceCollectionExtensions.cs deleted file mode 100644 index 88f063f72259..000000000000 --- a/src/Middleware/ConcurrencyLimiter/src/QueuePolicies/QueuePolicyServiceCollectionExtensions.cs +++ /dev/null @@ -1,40 +0,0 @@ -// Licensed to the .NET Foundation under one or more agreements. -// The .NET Foundation licenses this file to you under the MIT license. - -using Microsoft.AspNetCore.ConcurrencyLimiter; - -namespace Microsoft.Extensions.DependencyInjection; - -/// -/// Contains methods for specifying which queue the middleware should use. -/// -public static class QueuePolicyServiceCollectionExtensions -{ - /// - /// Tells to use a FIFO queue as its queueing strategy. - /// - /// The to add services to. - /// Set the options used by the queue. - /// Mandatory, since must be provided. - /// - public static IServiceCollection AddQueuePolicy(this IServiceCollection services, Action configure) - { - services.Configure(configure); - services.AddSingleton(); - return services; - } - - /// - /// Tells to use a LIFO stack as its queueing strategy. - /// - /// The to add services to. - /// Set the options used by the queue. - /// Mandatory, since must be provided. - /// - public static IServiceCollection AddStackPolicy(this IServiceCollection services, Action configure) - { - services.Configure(configure); - services.AddSingleton(); - return services; - } -} diff --git a/src/Middleware/ConcurrencyLimiter/src/QueuePolicies/StackPolicy.cs b/src/Middleware/ConcurrencyLimiter/src/QueuePolicies/StackPolicy.cs deleted file mode 100644 index 0dbd580ac374..000000000000 --- a/src/Middleware/ConcurrencyLimiter/src/QueuePolicies/StackPolicy.cs +++ /dev/null @@ -1,15 +0,0 @@ -// Licensed to the .NET Foundation under one or more agreements. -// The .NET Foundation licenses this file to you under the MIT license. - -using System.Threading.RateLimiting; -using Microsoft.Extensions.Options; - -namespace Microsoft.AspNetCore.ConcurrencyLimiter; - -internal sealed class StackPolicy : BasePolicy -{ - public StackPolicy(IOptions options) - : base(options, QueueProcessingOrder.NewestFirst) - { - } -} diff --git a/src/Middleware/ConcurrencyLimiter/test/ConcurrencyLimiterEventSourceTests.cs b/src/Middleware/ConcurrencyLimiter/test/ConcurrencyLimiterEventSourceTests.cs deleted file mode 100644 index 61b5b99b9d87..000000000000 --- a/src/Middleware/ConcurrencyLimiter/test/ConcurrencyLimiterEventSourceTests.cs +++ /dev/null @@ -1,137 +0,0 @@ -// Licensed to the .NET Foundation under one or more agreements. -// The .NET Foundation licenses this file to you under the MIT license. - -using System.Globalization; -using System.Diagnostics.Tracing; -using Microsoft.AspNetCore.Internal; -using Microsoft.AspNetCore.InternalTesting; -using Microsoft.Extensions.Logging; - -namespace Microsoft.AspNetCore.ConcurrencyLimiter.Tests; - -public class ConcurrencyLimiterEventSourceTests : LoggedTest -{ - [Fact] - public void MatchesNameAndGuid() - { - var eventSource = new ConcurrencyLimiterEventSource(); - - Assert.Equal("Microsoft.AspNetCore.ConcurrencyLimiter", eventSource.Name); - Assert.Equal(Guid.Parse("a605548a-6963-55cf-f000-99a6013deb01", CultureInfo.InvariantCulture), eventSource.Guid); - } - - [Fact] - public void RecordsRequestsRejected() - { - // Arrange - var expectedId = 1; - var eventListener = new TestEventListener(expectedId); - var eventSource = GetConcurrencyLimiterEventSource(); - eventListener.EnableEvents(eventSource, EventLevel.Informational); - - // Act - eventSource.RequestRejected(); - - // Assert - var eventData = eventListener.EventData; - Assert.NotNull(eventData); - Assert.Equal(expectedId, eventData.EventId); - Assert.Equal(EventLevel.Warning, eventData.Level); - Assert.Same(eventSource, eventData.EventSource); - Assert.Null(eventData.Message); - Assert.Empty(eventData.Payload); - } - - [Fact] - public async Task TracksQueueLength() - { - // Arrange - using var eventSource = GetConcurrencyLimiterEventSource(); - - using var eventListener = new TestCounterListener(LoggerFactory, eventSource.Name, [ - "queue-length", - "queue-duration", - "requests-rejected", - ]); - - using var timeoutTokenSource = new CancellationTokenSource(TimeSpan.FromSeconds(30)); - - var lengthValues = eventListener.GetCounterValues("queue-length", timeoutTokenSource.Token); - - eventListener.EnableEvents(eventSource, EventLevel.Informational, EventKeywords.None, - new Dictionary - { - {"EventCounterIntervalSec", ".1" } - }); - - // Act - eventSource.RequestRejected(); - - await WaitForCounterValue(lengthValues, expectedValue: 0, Logger); - using (eventSource.QueueTimer()) - { - await WaitForCounterValue(lengthValues, expectedValue: 1, Logger); - - using (eventSource.QueueTimer()) - { - await WaitForCounterValue(lengthValues, expectedValue: 2, Logger); - } - - await WaitForCounterValue(lengthValues, expectedValue: 1, Logger); - } - - await WaitForCounterValue(lengthValues, expectedValue: 0, Logger); - } - - [Fact] - public async Task TracksDurationSpentInQueue() - { - // Arrange - using var eventSource = GetConcurrencyLimiterEventSource(); - - using var eventListener = new TestCounterListener(LoggerFactory, eventSource.Name, [ - "queue-length", - "queue-duration", - "requests-rejected", - ]); - - using var timeoutTokenSource = new CancellationTokenSource(TimeSpan.FromSeconds(5)); - - var durationValues = eventListener.GetCounterValues("queue-duration", timeoutTokenSource.Token); - - eventListener.EnableEvents(eventSource, EventLevel.Informational, EventKeywords.None, - new Dictionary - { - {"EventCounterIntervalSec", ".1" } - }); - - // Act - await WaitForCounterValue(durationValues, expectedValue: 0, Logger); - - using (eventSource.QueueTimer()) - { - await WaitForCounterValue(durationValues, expectedValue: 0, Logger); - } - - // check that something (anything!) has been written - while (await durationValues.Values.MoveNextAsync()) - { - if (durationValues.Values.Current > 0) - { - return; - } - } - - throw new TimeoutException(); - } - - private static async Task WaitForCounterValue(CounterValues values, double expectedValue, ILogger logger) - { - await values.Values.WaitForValueAsync(expectedValue, values.CounterName, logger); - } - - private static ConcurrencyLimiterEventSource GetConcurrencyLimiterEventSource() - { - return new ConcurrencyLimiterEventSource(Guid.NewGuid().ToString()); - } -} diff --git a/src/Middleware/ConcurrencyLimiter/test/Microsoft.AspNetCore.ConcurrencyLimiter.Tests.csproj b/src/Middleware/ConcurrencyLimiter/test/Microsoft.AspNetCore.ConcurrencyLimiter.Tests.csproj deleted file mode 100644 index ad2734d58177..000000000000 --- a/src/Middleware/ConcurrencyLimiter/test/Microsoft.AspNetCore.ConcurrencyLimiter.Tests.csproj +++ /dev/null @@ -1,18 +0,0 @@ - - - - $(DefaultNetCoreTargetFramework) - - - - - - - - - - - - - - diff --git a/src/Middleware/ConcurrencyLimiter/test/MiddlewareTests.cs b/src/Middleware/ConcurrencyLimiter/test/MiddlewareTests.cs deleted file mode 100644 index b84689771284..000000000000 --- a/src/Middleware/ConcurrencyLimiter/test/MiddlewareTests.cs +++ /dev/null @@ -1,237 +0,0 @@ -// Licensed to the .NET Foundation under one or more agreements. -// The .NET Foundation licenses this file to you under the MIT license. - -using System.Threading.Tasks.Sources; -using Microsoft.AspNetCore.Http; -using Microsoft.AspNetCore.InternalTesting; - -namespace Microsoft.AspNetCore.ConcurrencyLimiter.Tests; - -public class MiddlewareTests -{ - [Fact] - public async Task RequestsCallNextIfQueueReturnsTrue() - { - var flag = false; - - var middleware = TestUtils.CreateTestMiddleware( - queue: TestQueue.AlwaysTrue, - next: httpContext => - { - flag = true; - return Task.CompletedTask; - }); - - await middleware.Invoke(new DefaultHttpContext()); - Assert.True(flag); - } - - [Fact] - public async Task RequestRejectsIfQueueReturnsFalse() - { - bool onRejectedInvoked = false; - - var middleware = TestUtils.CreateTestMiddleware( - queue: TestQueue.AlwaysFalse, - onRejected: httpContext => - { - onRejectedInvoked = true; - return Task.CompletedTask; - }); - - var context = new DefaultHttpContext(); - await middleware.Invoke(context).DefaultTimeout(); - Assert.True(onRejectedInvoked); - Assert.Equal(StatusCodes.Status503ServiceUnavailable, context.Response.StatusCode); - } - - [Fact] - public async Task RequestsDoesNotEnterIfQueueFull() - { - var middleware = TestUtils.CreateTestMiddleware( - queue: TestQueue.AlwaysFalse, - next: httpContext => - { - // throttle should bounce the request; it should never get here - throw new DivideByZeroException(); - }); - - await middleware.Invoke(new DefaultHttpContext()).DefaultTimeout(); - } - - [Fact] - public void IncomingRequestsFillUpQueue() - { - var testQueue = TestQueue.AlwaysBlock; - var middleware = TestUtils.CreateTestMiddleware(testQueue); - - Assert.Equal(0, testQueue.QueuedRequests); - - var task1 = middleware.Invoke(new DefaultHttpContext()); - Assert.Equal(1, testQueue.QueuedRequests); - Assert.False(task1.IsCompleted); - - var task2 = middleware.Invoke(new DefaultHttpContext()); - Assert.Equal(2, testQueue.QueuedRequests); - Assert.False(task2.IsCompleted); - } - - [Fact] - public void EventCountersTrackQueuedRequests() - { - var blocker = new TaskCompletionSource(); - - var testQueue = new TestQueue( - onTryEnter: async (_) => - { - return await blocker.Task; - }); - var middleware = TestUtils.CreateTestMiddleware(testQueue); - - Assert.Equal(0, testQueue.QueuedRequests); - - var task1 = middleware.Invoke(new DefaultHttpContext()); - Assert.False(task1.IsCompleted); - Assert.Equal(1, testQueue.QueuedRequests); - - blocker.SetResult(true); - - Assert.Equal(0, testQueue.QueuedRequests); - } - - [Fact] - public async Task QueueOnExitCalledEvenIfNextErrors() - { - var flag = false; - - var testQueue = new TestQueue( - onTryEnter: (_) => true, - onExit: () => { flag = true; }); - - var middleware = TestUtils.CreateTestMiddleware( - queue: testQueue, - next: httpContext => - { - throw new DivideByZeroException(); - }); - - Assert.Equal(0, testQueue.QueuedRequests); - await Assert.ThrowsAsync(() => middleware.Invoke(new DefaultHttpContext())).DefaultTimeout(); - - Assert.Equal(0, testQueue.QueuedRequests); - Assert.True(flag); - } - - [Fact] - public async Task ExceptionThrownDuringOnRejected() - { - TaskCompletionSource tcs = new TaskCompletionSource(); - - var concurrent = 0; - var testQueue = new TestQueue( - onTryEnter: (testQueue) => - { - if (concurrent > 0) - { - return false; - } - else - { - concurrent++; - return true; - } - }, - onExit: () => { concurrent--; }); - - var middleware = TestUtils.CreateTestMiddleware( - queue: testQueue, - onRejected: httpContext => - { - throw new DivideByZeroException(); - }, - next: httpContext => - { - return tcs.Task; - }); - - // the first request enters the server, and is blocked by the tcs - var firstRequest = middleware.Invoke(new DefaultHttpContext()); - Assert.Equal(1, concurrent); - Assert.Equal(0, testQueue.QueuedRequests); - - // the second request is rejected with a 503 error. During the rejection, an error occurs - var context = new DefaultHttpContext(); - await Assert.ThrowsAsync(() => middleware.Invoke(context)).DefaultTimeout(); - Assert.Equal(StatusCodes.Status503ServiceUnavailable, context.Response.StatusCode); - Assert.Equal(1, concurrent); - Assert.Equal(0, testQueue.QueuedRequests); - - // the first request is unblocked, and the queue continues functioning as expected - tcs.SetResult(); - Assert.True(firstRequest.IsCompletedSuccessfully); - Assert.Equal(0, concurrent); - Assert.Equal(0, testQueue.QueuedRequests); - - var thirdRequest = middleware.Invoke(new DefaultHttpContext()); - Assert.True(thirdRequest.IsCompletedSuccessfully); - Assert.Equal(0, concurrent); - Assert.Equal(0, testQueue.QueuedRequests); - } - - [Fact] - public async Task MiddlewareOnlyCallsGetResultOnce() - { - var flag = false; - - var queue = new TestQueueForValueTask(); - var middleware = TestUtils.CreateTestMiddleware( - queue, - next: async context => - { - await Task.CompletedTask; - flag = true; - }); - - await middleware.Invoke(new DefaultHttpContext()); - - Assert.True(flag); - } - - private class TestQueueForValueTask : IQueuePolicy - { - public TestValueResult Source; - public TestQueueForValueTask() - { - Source = new TestValueResult(); - } - - public ValueTask TryEnterAsync() - { - return new ValueTask(Source, 0); - } - - public void OnExit() { } - } - - private class TestValueResult : IValueTaskSource - { - private bool _getResultCalled; - - public bool GetResult(short token) - { - Assert.False(_getResultCalled); - _getResultCalled = true; - return true; - } - - public ValueTaskSourceStatus GetStatus(short token) - { - return ValueTaskSourceStatus.Succeeded; - } - - public void OnCompleted(Action continuation, object state, short token, ValueTaskSourceOnCompletedFlags flags) - { - throw new NotImplementedException(); - } - } -} diff --git a/src/Middleware/ConcurrencyLimiter/test/PolicyTests/QueuePolicyTests.cs b/src/Middleware/ConcurrencyLimiter/test/PolicyTests/QueuePolicyTests.cs deleted file mode 100644 index da34fdf83d64..000000000000 --- a/src/Middleware/ConcurrencyLimiter/test/PolicyTests/QueuePolicyTests.cs +++ /dev/null @@ -1,70 +0,0 @@ -// Licensed to the .NET Foundation under one or more agreements. -// The .NET Foundation licenses this file to you under the MIT license. - -using Microsoft.AspNetCore.InternalTesting; - -namespace Microsoft.AspNetCore.ConcurrencyLimiter.Tests.PolicyTests; - -public class QueuePolicyTests -{ - [Fact] - public void DoesNotWaitIfSpaceAvailable() - { - using var s = TestUtils.CreateQueuePolicy(2); - - var t1 = s.TryEnterAsync(); - Assert.True(t1.IsCompleted); - - var t2 = s.TryEnterAsync(); - Assert.True(t2.IsCompleted); - - var t3 = s.TryEnterAsync(); - Assert.False(t3.IsCompleted); - } - - [Fact] - public async Task WaitsIfNoSpaceAvailable() - { - using var s = TestUtils.CreateQueuePolicy(1); - Assert.True(await s.TryEnterAsync().DefaultTimeout()); - - var waitingTask = s.TryEnterAsync(); - Assert.False(waitingTask.IsCompleted); - - s.OnExit(); - Assert.True(await waitingTask.DefaultTimeout()); - } - - [Fact] - public void DoesNotWaitIfQueueFull() - { - using var s = TestUtils.CreateQueuePolicy(2, 1); - -#pragma warning disable xUnit1031 // Do not use blocking task operations in test method - var t1 = s.TryEnterAsync(); - Assert.True(t1.IsCompleted); - Assert.True(t1.Result); - - var t2 = s.TryEnterAsync(); - Assert.True(t2.IsCompleted); - Assert.True(t2.Result); - - var t3 = s.TryEnterAsync(); - Assert.False(t3.IsCompleted); - - var t4 = s.TryEnterAsync(); - Assert.True(t4.IsCompleted); - Assert.False(t4.Result); -#pragma warning restore xUnit1031 // Do not use blocking task operations in test method - } - - [Fact] - public async Task IsEncapsulated() - { - using var s1 = TestUtils.CreateQueuePolicy(1); - using var s2 = TestUtils.CreateQueuePolicy(1); - - Assert.True(await s1.TryEnterAsync().DefaultTimeout()); - Assert.True(await s2.TryEnterAsync().DefaultTimeout()); - } -} diff --git a/src/Middleware/ConcurrencyLimiter/test/PolicyTests/StackPolicyTests.cs b/src/Middleware/ConcurrencyLimiter/test/PolicyTests/StackPolicyTests.cs deleted file mode 100644 index 04c5fb1088fe..000000000000 --- a/src/Middleware/ConcurrencyLimiter/test/PolicyTests/StackPolicyTests.cs +++ /dev/null @@ -1,151 +0,0 @@ -// Licensed to the .NET Foundation under one or more agreements. -// The .NET Foundation licenses this file to you under the MIT license. - -using Microsoft.Extensions.Options; - -namespace Microsoft.AspNetCore.ConcurrencyLimiter.Tests.PolicyTests; - -public class StackPolicyTests -{ - [Fact] - public async Task BaseFunctionality() - { - var stack = new StackPolicy(Options.Create(new QueuePolicyOptions - { - MaxConcurrentRequests = 1, - RequestQueueLimit = 2, - })); - - var task1 = stack.TryEnterAsync(); - - Assert.True(task1.IsCompleted); - Assert.True(await task1); - - var task2 = stack.TryEnterAsync(); - - Assert.False(task2.IsCompleted); - - stack.OnExit(); - - Assert.True(await task2); - - stack.OnExit(); - } - - [Fact] - public async Task OldestRequestOverwritten() - { - var stack = new StackPolicy(Options.Create(new QueuePolicyOptions - { - MaxConcurrentRequests = 1, - RequestQueueLimit = 3, - })); - - var task1 = stack.TryEnterAsync(); - Assert.True(task1.IsCompleted); - - var task2 = stack.TryEnterAsync(); - Assert.False(task2.IsCompleted); - var task3 = stack.TryEnterAsync(); - Assert.False(task3.IsCompleted); - var task4 = stack.TryEnterAsync(); - Assert.False(task4.IsCompleted); - - var task5 = stack.TryEnterAsync(); - Assert.False(task5.IsCompleted); - - // Should have been pushed out of the stack - Assert.False(await task2); - - Assert.False(task3.IsCompleted); - Assert.False(task4.IsCompleted); - } - - [Fact] - public void RespectsMaxConcurrency() - { - var stack = new StackPolicy(Options.Create(new QueuePolicyOptions - { - MaxConcurrentRequests = 2, - RequestQueueLimit = 2, - })); - - var task1 = stack.TryEnterAsync(); - Assert.True(task1.IsCompleted); - - var task2 = stack.TryEnterAsync(); - Assert.True(task2.IsCompleted); - - var task3 = stack.TryEnterAsync(); - Assert.False(task3.IsCompleted); - } - - [Fact] - public async Task ExitRequestsPreserveSemaphoreState() - { - var stack = new StackPolicy(Options.Create(new QueuePolicyOptions - { - MaxConcurrentRequests = 1, - RequestQueueLimit = 2, - })); - - var task1 = stack.TryEnterAsync(); - Assert.True(task1.IsCompleted && await task1); - - var task2 = stack.TryEnterAsync(); - Assert.False(task2.IsCompleted); - - stack.OnExit(); // t1 exits, should free t2 to return - Assert.True(await task2); - - stack.OnExit(); // t2 exists, there's now a free spot in server - - var task3 = stack.TryEnterAsync(); - Assert.True(task3.IsCompleted && await task3); - } - - [Fact] - public async Task StaleRequestsAreProperlyOverwritten() - { - var stack = new StackPolicy(Options.Create(new QueuePolicyOptions - { - MaxConcurrentRequests = 1, - RequestQueueLimit = 4, - })); - - var task0 = stack.TryEnterAsync(); - Assert.True(task0.IsCompleted && await task0); - - var task1 = stack.TryEnterAsync(); - Assert.False(task1.IsCompleted); - - stack.OnExit(); - Assert.True(await task1); - - var task2 = stack.TryEnterAsync(); - Assert.False(task2.IsCompleted); - - stack.OnExit(); - Assert.True(await task2); - - stack.OnExit(); - } - - [Fact] - public async Task OneTryEnterAsyncOneOnExit() - { - var stack = new StackPolicy(Options.Create(new QueuePolicyOptions - { - MaxConcurrentRequests = 1, - RequestQueueLimit = 4, - })); - - Assert.Throws(() => stack.OnExit()); - - await stack.TryEnterAsync(); - - stack.OnExit(); - - Assert.Throws(() => stack.OnExit()); - } -} diff --git a/src/Middleware/ConcurrencyLimiter/test/TaskExtensions.cs b/src/Middleware/ConcurrencyLimiter/test/TaskExtensions.cs deleted file mode 100644 index bef370188b3f..000000000000 --- a/src/Middleware/ConcurrencyLimiter/test/TaskExtensions.cs +++ /dev/null @@ -1,35 +0,0 @@ -// Licensed to the .NET Foundation under one or more agreements. -// The .NET Foundation licenses this file to you under the MIT license. - -namespace System.Threading.Tasks; -#if TESTUTILS - public -#else -internal -#endif - static class TaskExtensions -{ - public static async Task OrThrowIfOtherFails(this Task task, Task otherTask) - { - var completed = await Task.WhenAny(task, otherTask); - if (completed == otherTask && otherTask.IsFaulted) - { - // Manifest the exception - otherTask.GetAwaiter().GetResult(); - throw new Exception("Unreachable code"); - } - else - { - // Await the task we were asked to await. Either it's finished, or the otherTask finished successfully, and it's not our job to check that - await task; - } - } - - public static async Task OrThrowIfOtherFails(this Task task, Task otherTask) - { - await OrThrowIfOtherFails((Task)task, otherTask); - - // If we get here, 'task' is finished and succeeded. - return task.GetAwaiter().GetResult(); - } -} diff --git a/src/Middleware/ConcurrencyLimiter/test/TestUtils.cs b/src/Middleware/ConcurrencyLimiter/test/TestUtils.cs deleted file mode 100644 index 76ce5073639b..000000000000 --- a/src/Middleware/ConcurrencyLimiter/test/TestUtils.cs +++ /dev/null @@ -1,114 +0,0 @@ -// Licensed to the .NET Foundation under one or more agreements. -// The .NET Foundation licenses this file to you under the MIT license. - -using Microsoft.AspNetCore.Http; -using Microsoft.Extensions.Logging.Abstractions; -using Microsoft.Extensions.Options; - -namespace Microsoft.AspNetCore.ConcurrencyLimiter.Tests; - -public static class TestUtils -{ -#pragma warning disable CS0618 // Type or member is obsolete - public static ConcurrencyLimiterMiddleware CreateTestMiddleware(IQueuePolicy queue = null, RequestDelegate onRejected = null, RequestDelegate next = null) - { - var options = Options.Create(new ConcurrencyLimiterOptions - { - OnRejected = onRejected ?? (context => Task.CompletedTask), - }); - - return new ConcurrencyLimiterMiddleware( - next: next ?? (context => Task.CompletedTask), - loggerFactory: NullLoggerFactory.Instance, - queue: queue ?? CreateQueuePolicy(1, 0), - options: options - ); - } - - public static ConcurrencyLimiterMiddleware CreateTestMiddleware_QueuePolicy(int maxConcurrentRequests, int requestQueueLimit, RequestDelegate onRejected = null, RequestDelegate next = null) - { - return CreateTestMiddleware( - queue: CreateQueuePolicy(maxConcurrentRequests, requestQueueLimit), - onRejected: onRejected, - next: next - ); - } - - public static ConcurrencyLimiterMiddleware CreateTestMiddleware_StackPolicy(int maxConcurrentRequests, int requestQueueLimit, RequestDelegate onRejected = null, RequestDelegate next = null) - { - return CreateTestMiddleware( - queue: CreateStackPolicy(maxConcurrentRequests, requestQueueLimit), - onRejected: onRejected, - next: next - ); - } - - internal static StackPolicy CreateStackPolicy(int maxConcurrentRequests, int requestsQueuelimit = 100) - { - var options = Options.Create(new QueuePolicyOptions - { - MaxConcurrentRequests = maxConcurrentRequests, - RequestQueueLimit = requestsQueuelimit - }); - - return new StackPolicy(options); - } - - internal static QueuePolicy CreateQueuePolicy(int maxConcurrentRequests, int requestQueueLimit = 100) - { - var options = Options.Create(new QueuePolicyOptions - { - MaxConcurrentRequests = maxConcurrentRequests, - RequestQueueLimit = requestQueueLimit - }); - - return new QueuePolicy(options); - } -#pragma warning restore CS0618 // Type or member is obsolete -} - -internal class TestQueue : IQueuePolicy -{ - private Func> _onTryEnter { get; } - private Action _onExit { get; } - - private int _queuedRequests; - public int QueuedRequests { get => _queuedRequests; } - - public TestQueue(Func> onTryEnter, Action onExit = null) - { - _onTryEnter = onTryEnter; - _onExit = onExit ?? (() => { }); - } - - public TestQueue(Func onTryEnter, Action onExit = null) : - this(state => Task.FromResult(onTryEnter(state)) - , onExit) - { } - - public async ValueTask TryEnterAsync() - { - Interlocked.Increment(ref _queuedRequests); - var result = await _onTryEnter(this); - Interlocked.Decrement(ref _queuedRequests); - return result; - } - - public void OnExit() - { - _onExit(); - } - - public static TestQueue AlwaysFalse = - new TestQueue((_) => false); - - public static TestQueue AlwaysTrue = - new TestQueue((_) => true); - - public static TestQueue AlwaysBlock = - new TestQueue(async (_) => - { - await new SemaphoreSlim(0).WaitAsync(); - return false; - }); -} diff --git a/src/Middleware/Middleware.slnf b/src/Middleware/Middleware.slnf index 1224b4f0c41b..f4b91bdaaa67 100644 --- a/src/Middleware/Middleware.slnf +++ b/src/Middleware/Middleware.slnf @@ -42,10 +42,6 @@ "src\\Middleware\\CORS\\src\\Microsoft.AspNetCore.Cors.csproj", "src\\Middleware\\CORS\\test\\UnitTests\\Microsoft.AspNetCore.Cors.Test.csproj", "src\\Middleware\\CORS\\test\\testassets\\CorsMiddlewareWebSite\\CorsMiddlewareWebSite.csproj", - "src\\Middleware\\ConcurrencyLimiter\\perf\\Microbenchmarks\\Microsoft.AspNetCore.ConcurrencyLimiter.Microbenchmarks.csproj", - "src\\Middleware\\ConcurrencyLimiter\\sample\\ConcurrencyLimiterSample.csproj", - "src\\Middleware\\ConcurrencyLimiter\\src\\Microsoft.AspNetCore.ConcurrencyLimiter.csproj", - "src\\Middleware\\ConcurrencyLimiter\\test\\Microsoft.AspNetCore.ConcurrencyLimiter.Tests.csproj", "src\\Middleware\\Diagnostics.Abstractions\\src\\Microsoft.AspNetCore.Diagnostics.Abstractions.csproj", "src\\Middleware\\Diagnostics.EntityFrameworkCore\\src\\Microsoft.AspNetCore.Diagnostics.EntityFrameworkCore.csproj", "src\\Middleware\\Diagnostics.EntityFrameworkCore\\test\\FunctionalTests\\Diagnostics.EFCore.FunctionalTests.csproj", diff --git a/src/Tools/Tools.slnf b/src/Tools/Tools.slnf index c8b8ef0269ba..e1591cdaeb8d 100644 --- a/src/Tools/Tools.slnf +++ b/src/Tools/Tools.slnf @@ -49,7 +49,6 @@ "src\\JSInterop\\Microsoft.JSInterop\\src\\Microsoft.JSInterop.csproj", "src\\Localization\\Abstractions\\src\\Microsoft.Extensions.Localization.Abstractions.csproj", "src\\Middleware\\CORS\\src\\Microsoft.AspNetCore.Cors.csproj", - "src\\Middleware\\ConcurrencyLimiter\\src\\Microsoft.AspNetCore.ConcurrencyLimiter.csproj", "src\\Middleware\\Diagnostics.Abstractions\\src\\Microsoft.AspNetCore.Diagnostics.Abstractions.csproj", "src\\Middleware\\Diagnostics.EntityFrameworkCore\\src\\Microsoft.AspNetCore.Diagnostics.EntityFrameworkCore.csproj", "src\\Middleware\\Diagnostics\\src\\Microsoft.AspNetCore.Diagnostics.csproj",