diff --git a/src/Middleware/ConcurrencyLimiter/src/ConcurrencyLimiterEndpointConventionBuilderExtensions.cs b/src/Middleware/ConcurrencyLimiter/src/ConcurrencyLimiterEndpointConventionBuilderExtensions.cs
new file mode 100644
index 000000000000..405bdd8ef0e0
--- /dev/null
+++ b/src/Middleware/ConcurrencyLimiter/src/ConcurrencyLimiterEndpointConventionBuilderExtensions.cs
@@ -0,0 +1,103 @@
+// Copyright (c) .NET Foundation. All rights reserved.
+// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
+
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Text;
+using System.Threading.Tasks;
+using Microsoft.AspNetCore.ConcurrencyLimiter;
+using Microsoft.Extensions.Options;
+
+namespace Microsoft.AspNetCore.Builder
+{
+ ///
+ /// Concurrency limit extension methods for
+ ///
+ public static class ConcurrencyLimiterEndpointConventionBuilderExtensions
+ {
+ ///
+ /// Adds the concurrency limit with LIFO stack as queueing strategy to the endpoint(s).
+ ///
+ ///
+ /// The .
+ ///
+ /// 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.
+ ///
+ ///
+ ///Maximum number of queued requests before the server starts rejecting connections with '503 Service Unavailible'.
+ /// This option is highly application dependant, and must be configured by the application.
+ ///
+ ///
+ public static TBuilder RequireStackPolicy(this TBuilder builder,
+ int maxConcurrentRequests,
+ int requestQueueLimit)
+ where TBuilder : IEndpointConventionBuilder
+ {
+ if (builder == null)
+ {
+ throw new ArgumentNullException(nameof(builder));
+ }
+
+ builder.Add(endpoints =>
+ {
+ endpoints.Metadata.Add(new StackPolicyAttribute(maxConcurrentRequests, requestQueueLimit));
+ });
+
+ return builder;
+ }
+ ///
+ /// Adds the concurrency limit with FIFO queue as queueing strategy to the endpoint(s).
+ ///
+ ///
+ /// The .
+ ///
+ /// 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.
+ ///
+ ///
+ ///Maximum number of queued requests before the server starts rejecting connections with '503 Service Unavailible'.
+ /// This option is highly application dependant, and must be configured by the application.
+ ///
+ ///
+ public static TBuilder RequireQueuePolicy(this TBuilder builder,
+ int maxConcurrentRequests,
+ int requestQueueLimit)
+ where TBuilder : IEndpointConventionBuilder
+ {
+ if (builder == null)
+ {
+ throw new ArgumentNullException(nameof(builder));
+ }
+
+ builder.Add(endpoints =>
+ {
+ endpoints.Metadata.Add(new QueuePolicyAttribute(maxConcurrentRequests, requestQueueLimit));
+ });
+
+ return builder;
+ }
+ ///
+ /// Suppresses the concurrency limit to the endpoint(s).
+ ///
+ ///
+ /// The .
+ ///
+ public static TBuilder SupressQueuePolicy(this TBuilder builder)
+ where TBuilder : IEndpointConventionBuilder
+ {
+ if (builder == null)
+ {
+ throw new ArgumentNullException(nameof(builder));
+ }
+
+ builder.Add(endpoints =>
+ {
+ endpoints.Metadata.Add(new SuppressQueuePolicyAttribute());
+ });
+
+ return builder;
+ }
+ }
+}
diff --git a/src/Middleware/ConcurrencyLimiter/src/ConcurrencyLimiterMiddleware.cs b/src/Middleware/ConcurrencyLimiter/src/ConcurrencyLimiterMiddleware.cs
index 16c612cae9a1..2bdae0b6dfa9 100644
--- a/src/Middleware/ConcurrencyLimiter/src/ConcurrencyLimiterMiddleware.cs
+++ b/src/Middleware/ConcurrencyLimiter/src/ConcurrencyLimiterMiddleware.cs
@@ -2,6 +2,7 @@
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
using System;
+using System.Linq;
using System.Threading.Tasks;
using Microsoft.AspNetCore.Http;
using Microsoft.Extensions.Logging;
@@ -46,7 +47,19 @@ public ConcurrencyLimiterMiddleware(RequestDelegate next, ILoggerFactory loggerF
/// A that completes when the request leaves.
public async Task Invoke(HttpContext context)
{
- var waitInQueueTask = _queuePolicy.TryEnterAsync();
+ var endpoint = context.GetEndpoint();
+
+ if (endpoint?.Metadata.GetMetadata() != null)
+ {
+ await _next(context);
+
+ return;
+ }
+
+ var queuePolicy = endpoint?.Metadata.GetMetadata()
+ ?? _queuePolicy;
+
+ var waitInQueueTask = queuePolicy.TryEnterAsync();
// Make sure we only ever call GetResult once on the TryEnterAsync ValueTask b/c it resets.
bool result;
@@ -72,7 +85,7 @@ public async Task Invoke(HttpContext context)
}
finally
{
- _queuePolicy.OnExit();
+ queuePolicy.OnExit();
}
}
else
diff --git a/src/Middleware/ConcurrencyLimiter/src/ISuppressQueuePolicyMetadata.cs b/src/Middleware/ConcurrencyLimiter/src/ISuppressQueuePolicyMetadata.cs
new file mode 100644
index 000000000000..bfa84b45d385
--- /dev/null
+++ b/src/Middleware/ConcurrencyLimiter/src/ISuppressQueuePolicyMetadata.cs
@@ -0,0 +1,12 @@
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Text;
+using System.Threading.Tasks;
+
+namespace Microsoft.AspNetCore.ConcurrencyLimiter
+{
+ public interface ISuppressQueuePolicyMetadata
+ {
+ }
+}
diff --git a/src/Middleware/ConcurrencyLimiter/src/QueuePolicies/QueuePolicyServiceCollectionExtensions.cs b/src/Middleware/ConcurrencyLimiter/src/QueuePolicies/QueuePolicyServiceCollectionExtensions.cs
index 09b9aeb48ff5..b9d9c88b38b0 100644
--- a/src/Middleware/ConcurrencyLimiter/src/QueuePolicies/QueuePolicyServiceCollectionExtensions.cs
+++ b/src/Middleware/ConcurrencyLimiter/src/QueuePolicies/QueuePolicyServiceCollectionExtensions.cs
@@ -12,7 +12,7 @@ namespace Microsoft.Extensions.DependencyInjection
public static class QueuePolicyServiceCollectionExtensions
{
///
- /// Tells to use a FIFO queue as its queueing strategy.
+ /// Tells to use a FIFO queue as its default queueing strategy.
///
/// The to add services to.
/// Set the options used by the queue.
@@ -26,7 +26,7 @@ public static IServiceCollection AddQueuePolicy(this IServiceCollection services
}
///
- /// Tells to use a LIFO stack as its queueing strategy.
+ /// Tells to use a LIFO stack as its default queueing strategy.
///
/// The to add services to.
/// Set the options used by the queue.
diff --git a/src/Middleware/ConcurrencyLimiter/src/QueuePolicyAttribute.cs b/src/Middleware/ConcurrencyLimiter/src/QueuePolicyAttribute.cs
new file mode 100644
index 000000000000..38732f35599b
--- /dev/null
+++ b/src/Middleware/ConcurrencyLimiter/src/QueuePolicyAttribute.cs
@@ -0,0 +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 System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Text;
+using System.Threading.Tasks;
+using Microsoft.Extensions.Options;
+
+namespace Microsoft.AspNetCore.ConcurrencyLimiter
+{
+ ///
+ /// Specifies that the class or method that this attribute applied to requires limit concurrency request with FIFO queue as queueing strategy.
+ ///
+ [AttributeUsage(AttributeTargets.Class | AttributeTargets.Method, AllowMultiple = false, Inherited = true)]
+ public class QueuePolicyAttribute : Attribute, IQueuePolicy
+ {
+ private readonly QueuePolicy _queuePolicy;
+ ///
+ /// Initializes a new instance of the class.
+ ///
+ ///
+ /// 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.
+ ///
+ ///
+ ///Maximum number of queued requests before the server starts rejecting connections with '503 Service Unavailible'.
+ /// This option is highly application dependant, and must be configured by the application.
+ ///
+ public QueuePolicyAttribute(int maxConcurrentRequests, int requestQueueLimit)
+ {
+ _queuePolicy = new QueuePolicy(Options.Create(new QueuePolicyOptions()
+ {
+ MaxConcurrentRequests = maxConcurrentRequests,
+ RequestQueueLimit = requestQueueLimit
+ }));
+ }
+ ///
+ public void OnExit()
+ => _queuePolicy.OnExit();
+
+
+ ///
+ public ValueTask TryEnterAsync()
+ => _queuePolicy.TryEnterAsync();
+ }
+}
diff --git a/src/Middleware/ConcurrencyLimiter/src/StackPolicyAttribute.cs b/src/Middleware/ConcurrencyLimiter/src/StackPolicyAttribute.cs
new file mode 100644
index 000000000000..9a3d5f21b9a8
--- /dev/null
+++ b/src/Middleware/ConcurrencyLimiter/src/StackPolicyAttribute.cs
@@ -0,0 +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 System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Text;
+using System.Threading.Tasks;
+using Microsoft.Extensions.Options;
+
+namespace Microsoft.AspNetCore.ConcurrencyLimiter
+{
+ ///
+ /// Specifies that the class or method that this attribute applied to requires limit concurrency request with LIFO stack as queueing strategy.
+ ///
+ [AttributeUsage(AttributeTargets.Class | AttributeTargets.Method, AllowMultiple = false, Inherited = true)]
+ public class StackPolicyAttribute : Attribute, IQueuePolicy
+ {
+ private readonly StackPolicy _stackPolicy;
+ ///
+ /// Initializes a new instance of the class.
+ ///
+ ///
+ /// 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.
+ ///
+ ///
+ ///Maximum number of queued requests before the server starts rejecting connections with '503 Service Unavailible'.
+ /// This option is highly application dependant, and must be configured by the application.
+ ///
+ public StackPolicyAttribute(int maxConcurrentRequests, int requestQueueLimit)
+ {
+ _stackPolicy = new StackPolicy(Options.Create(new QueuePolicyOptions()
+ {
+ MaxConcurrentRequests = maxConcurrentRequests,
+ RequestQueueLimit = requestQueueLimit
+ }));
+ }
+
+ ///
+ public void OnExit()
+ => _stackPolicy.OnExit();
+
+ ///
+ public ValueTask TryEnterAsync()
+ => _stackPolicy.TryEnterAsync();
+ }
+}
diff --git a/src/Middleware/ConcurrencyLimiter/src/SuppressQueuePolicyAttribute.cs b/src/Middleware/ConcurrencyLimiter/src/SuppressQueuePolicyAttribute.cs
new file mode 100644
index 000000000000..c1b594e7e974
--- /dev/null
+++ b/src/Middleware/ConcurrencyLimiter/src/SuppressQueuePolicyAttribute.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 System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Text;
+using System.Threading.Tasks;
+
+namespace Microsoft.AspNetCore.ConcurrencyLimiter
+{
+ ///
+ /// Specifies that the class or method that this attribute applied to does not limit concurrency request.
+ ///
+ [AttributeUsage(AttributeTargets.Class | AttributeTargets.Method, AllowMultiple = false, Inherited = true)]
+ public class SuppressQueuePolicyAttribute : Attribute, ISuppressQueuePolicyMetadata
+ {
+ }
+}
diff --git a/src/Middleware/ConcurrencyLimiter/test/ConcurrencyLimiterEndpointConventionBuilderExtensionsTests.cs b/src/Middleware/ConcurrencyLimiter/test/ConcurrencyLimiterEndpointConventionBuilderExtensionsTests.cs
new file mode 100644
index 000000000000..e167b1069c24
--- /dev/null
+++ b/src/Middleware/ConcurrencyLimiter/test/ConcurrencyLimiterEndpointConventionBuilderExtensionsTests.cs
@@ -0,0 +1,76 @@
+// Copyright (c) .NET Foundation. All rights reserved.
+// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
+
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Text;
+using System.Threading.Tasks;
+using Microsoft.AspNetCore.Builder;
+using Microsoft.AspNetCore.Routing;
+using Microsoft.AspNetCore.Routing.Patterns;
+using Xunit;
+
+namespace Microsoft.AspNetCore.ConcurrencyLimiter.Tests
+{
+ public class ConcurrencyLimiterEndpointConventionBuilderExtensionsTests
+ {
+ [Fact]
+ public void RequireStackPolicy_StackPolicyAttribute()
+ {
+ // Arrange
+ var builder = new TestEndpointConventionBuilder();
+
+ // Act
+ builder.RequireStackPolicy(maxConcurrentRequests: 1, requestQueueLimit: 1);
+
+ // Assert
+ var convention = Assert.Single(builder.Conventions);
+ var endpoint = new RouteEndpointBuilder(context => Task.CompletedTask, RoutePatternFactory.Parse("/"), 0);
+ convention(endpoint);
+
+ Assert.IsAssignableFrom(Assert.Single(endpoint.Metadata));
+ }
+ [Fact]
+ public void RequireQueuePolicy_QueuePolicyAttribute()
+ {
+ // Arrange
+ var builder = new TestEndpointConventionBuilder();
+
+ // Act
+ builder.RequireQueuePolicy(maxConcurrentRequests: 1, requestQueueLimit: 1);
+
+ // Assert
+ var convention = Assert.Single(builder.Conventions);
+ var endpoint = new RouteEndpointBuilder(context => Task.CompletedTask, RoutePatternFactory.Parse("/"), 0);
+ convention(endpoint);
+
+ Assert.IsAssignableFrom(Assert.Single(endpoint.Metadata));
+ }
+ [Fact]
+ public void SupressQueuePolicy_ISuppressQueuePolicyMetadata()
+ {
+ // Arrange
+ var builder = new TestEndpointConventionBuilder();
+
+ // Act
+ builder.SupressQueuePolicy();
+
+ // Assert
+ var convention = Assert.Single(builder.Conventions);
+ var endpoint = new RouteEndpointBuilder(context => Task.CompletedTask, RoutePatternFactory.Parse("/"), 0);
+ convention(endpoint);
+
+ Assert.IsAssignableFrom(Assert.Single(endpoint.Metadata));
+ }
+ private class TestEndpointConventionBuilder : IEndpointConventionBuilder
+ {
+ public IList> Conventions { get; } = new List>();
+
+ public void Add(Action convention)
+ {
+ Conventions.Add(convention);
+ }
+ }
+ }
+}
diff --git a/src/Middleware/ConcurrencyLimiter/test/Microsoft.AspNetCore.ConcurrencyLimiter.Tests.csproj b/src/Middleware/ConcurrencyLimiter/test/Microsoft.AspNetCore.ConcurrencyLimiter.Tests.csproj
index 5bb19e39bebf..48f2d6c52a1b 100644
--- a/src/Middleware/ConcurrencyLimiter/test/Microsoft.AspNetCore.ConcurrencyLimiter.Tests.csproj
+++ b/src/Middleware/ConcurrencyLimiter/test/Microsoft.AspNetCore.ConcurrencyLimiter.Tests.csproj
@@ -1,4 +1,4 @@
-
+
$(DefaultNetCoreTargetFramework)
@@ -13,5 +13,6 @@
+
diff --git a/src/Middleware/ConcurrencyLimiter/test/MiddlewareTests.cs b/src/Middleware/ConcurrencyLimiter/test/MiddlewareTests.cs
index 79e04036db36..3d24ab342a3b 100644
--- a/src/Middleware/ConcurrencyLimiter/test/MiddlewareTests.cs
+++ b/src/Middleware/ConcurrencyLimiter/test/MiddlewareTests.cs
@@ -10,6 +10,47 @@ namespace Microsoft.AspNetCore.ConcurrencyLimiter.Tests
{
public class MiddlewareTests
{
+ [Fact]
+ public async Task RequestCallNextIfSuppressLimiter()
+ {
+ var flag = false;
+
+ var middleware = TestUtils.CreateTestMiddleware(
+ queue: TestQueue.AlwaysFalse,
+ next: httpContext =>
+ {
+ flag = true;
+ return Task.CompletedTask;
+ });
+
+ var httpContext = new DefaultHttpContext();
+ httpContext.SetEndpoint(CreateEndpoint(new SuppressQueuePolicyAttribute()));
+
+ await middleware.Invoke(httpContext);
+
+ Assert.True(flag);
+ }
+ [Fact]
+ public async Task RequestCallNextIfMetadataQueuePolicyReturnsTrue()
+ {
+ var flag = false;
+
+ var middleware = TestUtils.CreateTestMiddleware(
+ queue: TestQueue.AlwaysFalse,
+ next: httpContext =>
+ {
+ flag = true;
+ return Task.CompletedTask;
+ });
+
+ var httpContext = new DefaultHttpContext();
+ httpContext.SetEndpoint(CreateEndpoint(TestQueue.AlwaysTrue));
+
+ await middleware.Invoke(httpContext);
+
+ Assert.True(flag);
+ }
+
[Fact]
public async Task RequestsCallNextIfQueueReturnsTrue()
{
@@ -199,6 +240,11 @@ public async Task MiddlewareOnlyCallsGetResultOnce()
Assert.True(flag);
}
+ private Endpoint CreateEndpoint(params object[] metadata)
+ {
+ return new Endpoint(context => Task.CompletedTask, new EndpointMetadataCollection(metadata), "Test endpoint");
+ }
+
private class TestQueueForResettableBoolean : IQueuePolicy
{
public ResettableBooleanCompletionSource Source;
diff --git a/src/Middleware/ConcurrencyLimiter/test/QueuePolicyAttributeTests.cs b/src/Middleware/ConcurrencyLimiter/test/QueuePolicyAttributeTests.cs
new file mode 100644
index 000000000000..a884f1dafcdb
--- /dev/null
+++ b/src/Middleware/ConcurrencyLimiter/test/QueuePolicyAttributeTests.cs
@@ -0,0 +1,50 @@
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Text;
+using System.Threading.Tasks;
+using Xunit;
+
+namespace Microsoft.AspNetCore.ConcurrencyLimiter.Tests
+{
+ public class QueuePolicyAttributeTests
+ {
+ [Fact]
+ public void DoesNotWaitIfSpaceAvailible()
+ {
+ var s = TestUtils.CreateQueuePolicyAttribute(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 WaitsIfNoSpaceAvailible()
+ {
+ var s = TestUtils.CreateQueuePolicyAttribute(1);
+ Assert.True(await s.TryEnterAsync().OrTimeout());
+
+ var waitingTask = s.TryEnterAsync();
+ Assert.False(waitingTask.IsCompleted);
+
+ s.OnExit();
+ Assert.True(await waitingTask.OrTimeout());
+ }
+
+ [Fact]
+ public async Task IsEncapsulated()
+ {
+ var s1 = TestUtils.CreateQueuePolicyAttribute(1);
+ var s2 = TestUtils.CreateQueuePolicy(1);
+
+ Assert.True(await s1.TryEnterAsync().OrTimeout());
+ Assert.True(await s2.TryEnterAsync().OrTimeout());
+ }
+ }
+}
diff --git a/src/Middleware/ConcurrencyLimiter/test/StackPolicyAttributeTests.cs b/src/Middleware/ConcurrencyLimiter/test/StackPolicyAttributeTests.cs
new file mode 100644
index 000000000000..2f17fccc5310
--- /dev/null
+++ b/src/Middleware/ConcurrencyLimiter/test/StackPolicyAttributeTests.cs
@@ -0,0 +1,114 @@
+// Copyright (c) .NET Foundation. All rights reserved.
+// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
+
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Text;
+using System.Threading.Tasks;
+using Xunit;
+
+namespace Microsoft.AspNetCore.ConcurrencyLimiter.Tests
+{
+ public class StackPolicyAttributeTests
+ {
+
+ [Fact]
+ public static void BaseFunctionality()
+ {
+ var stack = TestUtils.CreateStackPolicyAttribute(0, 2);
+
+ var task1 = stack.TryEnterAsync();
+
+ Assert.False(task1.IsCompleted);
+
+ stack.OnExit();
+
+ Assert.True(task1.IsCompleted && task1.Result);
+ }
+
+ [Fact]
+ public static void OldestRequestOverwritten()
+ {
+ var stack = TestUtils.CreateStackPolicyAttribute(0, 3);
+
+ var task1 = stack.TryEnterAsync();
+ Assert.False(task1.IsCompleted);
+ var task2 = stack.TryEnterAsync();
+ Assert.False(task2.IsCompleted);
+ var task3 = stack.TryEnterAsync();
+ Assert.False(task3.IsCompleted);
+
+ var task4 = stack.TryEnterAsync();
+
+ Assert.True(task1.IsCompleted);
+ Assert.False(task1.Result);
+
+ Assert.False(task2.IsCompleted);
+ Assert.False(task3.IsCompleted);
+ Assert.False(task4.IsCompleted);
+ }
+
+ [Fact]
+ public static void RespectsMaxConcurrency()
+ {
+ var stack = TestUtils.CreateStackPolicyAttribute(2, 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 static void ExitRequestsPreserveSemaphoreState()
+ {
+ var stack = TestUtils.CreateStackPolicyAttribute(1, 2);
+ var task1 = stack.TryEnterAsync();
+ Assert.True(task1.IsCompleted && task1.Result);
+
+ var task2 = stack.TryEnterAsync();
+ Assert.False(task2.IsCompleted);
+
+ stack.OnExit(); // t1 exits, should free t2 to return
+ Assert.True(task2.IsCompleted && task2.Result);
+
+ stack.OnExit(); // t2 exists, there's now a free spot in server
+
+ var task3 = stack.TryEnterAsync();
+ Assert.True(task3.IsCompleted && task3.Result);
+ }
+
+ [Fact]
+ public static void StaleRequestsAreProperlyOverwritten()
+ {
+ var stack = TestUtils.CreateStackPolicyAttribute(0, 4);
+
+ var task1 = stack.TryEnterAsync();
+ stack.OnExit();
+ Assert.True(task1.IsCompleted);
+
+ var task2 = stack.TryEnterAsync();
+ stack.OnExit();
+ Assert.True(task2.IsCompleted);
+ }
+
+ [Fact]
+ public static async Task OneTryEnterAsyncOneOnExit()
+ {
+ var stack = TestUtils.CreateStackPolicyAttribute(1, 4);
+
+ Assert.Throws(() => stack.OnExit());
+
+ await stack.TryEnterAsync();
+
+ stack.OnExit();
+
+ Assert.Throws(() => stack.OnExit());
+ }
+ }
+}
diff --git a/src/Middleware/ConcurrencyLimiter/test/TestUtils.cs b/src/Middleware/ConcurrencyLimiter/test/TestUtils.cs
index 4ad25c5c5710..909d211b4a2b 100644
--- a/src/Middleware/ConcurrencyLimiter/test/TestUtils.cs
+++ b/src/Middleware/ConcurrencyLimiter/test/TestUtils.cs
@@ -66,6 +66,16 @@ internal static QueuePolicy CreateQueuePolicy(int maxConcurrentRequests, int req
return new QueuePolicy(options);
}
+
+ internal static QueuePolicyAttribute CreateQueuePolicyAttribute(int maxConcurrentRequests, int requestQueueLimit = 100)
+ {
+ return new QueuePolicyAttribute(maxConcurrentRequests, requestQueueLimit);
+ }
+
+ internal static StackPolicyAttribute CreateStackPolicyAttribute(int maxConcurrentRequests, int requestQueueLimit = 100)
+ {
+ return new StackPolicyAttribute(maxConcurrentRequests, requestQueueLimit);
+ }
}
internal class TestQueue : IQueuePolicy