Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -13,21 +13,21 @@ public class QueueEmptyOverhead
{
private const int _numRequests = 20000;

private ConcurrencyLimiterMiddleware _middlewareFIFO;
private ConcurrencyLimiterMiddleware _middlewareLIFO;
private ConcurrencyLimiterMiddleware _middlewareQueue;
private ConcurrencyLimiterMiddleware _middlewareStack;
private RequestDelegate _restOfServer;

[GlobalSetup]
public void GlobalSetup()
{
_restOfServer = YieldsThreadInternally ? (RequestDelegate)YieldsThread : (RequestDelegate)CompletesImmediately;

_middlewareFIFO = TestUtils.CreateTestMiddleware_TailDrop(
_middlewareQueue = TestUtils.CreateTestMiddleware_QueuePolicy(
maxConcurrentRequests: 1,
requestQueueLimit: 100,
next: _restOfServer);

_middlewareLIFO = TestUtils.CreateTestMiddleware_StackPolicy(
_middlewareStack = TestUtils.CreateTestMiddleware_StackPolicy(
maxConcurrentRequests: 1,
requestQueueLimit: 100,
next: _restOfServer);
Expand All @@ -46,20 +46,20 @@ public async Task Baseline()
}

[Benchmark(OperationsPerInvoke = _numRequests)]
public async Task WithEmptyQueueOverhead_FIFO()
public async Task WithEmptyQueueOverhead_QueuePolicy()
{
for (int i = 0; i < _numRequests; i++)
{
await _middlewareFIFO.Invoke(null);
await _middlewareQueue.Invoke(null);
}
}

[Benchmark(OperationsPerInvoke = _numRequests)]
public async Task WithEmptyQueueOverhead_LIFO()
public async Task WithEmptyQueueOverhead_StackPolicy()
{
for (int i = 0; i < _numRequests; i++)
{
await _middlewareLIFO.Invoke(null);
await _middlewareStack.Invoke(null);
}
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,32 +4,32 @@
using System.Threading;
using System.Threading.Tasks;
using BenchmarkDotNet.Attributes;
using Microsoft.AspNetCore.Http;
using Microsoft.AspNetCore.ConcurrencyLimiter.Tests;
using Microsoft.AspNetCore.Http;

namespace Microsoft.AspNetCore.ConcurrencyLimiter.Microbenchmarks
{
public class QueueFullOverhead
{
private const int _numRequests = 200;
private const int _numRequests = 2000;
private int _requestCount = 0;
private ManualResetEventSlim _mres = new ManualResetEventSlim();

private ConcurrencyLimiterMiddleware _middleware_FIFO;
private ConcurrencyLimiterMiddleware _middleware_LIFO;
private ConcurrencyLimiterMiddleware _middlewareQueue;
private ConcurrencyLimiterMiddleware _middlewareStack;

[Params(8)]
public int MaxConcurrentRequests;

[GlobalSetup]
public void GlobalSetup()
{
_middleware_FIFO = TestUtils.CreateTestMiddleware_TailDrop(
_middlewareQueue = TestUtils.CreateTestMiddleware_QueuePolicy(
maxConcurrentRequests: MaxConcurrentRequests,
requestQueueLimit: _numRequests,
next: IncrementAndCheck);

_middleware_LIFO = TestUtils.CreateTestMiddleware_StackPolicy(
_middlewareStack = TestUtils.CreateTestMiddleware_StackPolicy(
maxConcurrentRequests: MaxConcurrentRequests,
requestQueueLimit: _numRequests,
next: IncrementAndCheck);
Expand Down Expand Up @@ -64,26 +64,25 @@ public void Baseline()
}

[Benchmark(OperationsPerInvoke = _numRequests)]
public void QueueingAll_FIFO()
public void QueueingAll_QueuePolicy()
{
for (int i = 0; i < _numRequests; i++)
{
_ = _middleware_FIFO.Invoke(null);
_ = _middlewareStack.Invoke(null);
}

_mres.Wait();
}

[Benchmark(OperationsPerInvoke = _numRequests)]
public void QueueingAll_LIFO()
public void QueueingAll_StackPolicy()
{
for (int i = 0; i < _numRequests; i++)
{
_ = _middleware_LIFO.Invoke(null);
_ = _middlewareQueue.Invoke(null);
}

_mres.Wait();
}

}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,96 @@
// 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;
using System.Threading.Tasks;
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 int _queueLength = 20;
private int _rejectionCount = 0;
private ManualResetEventSlim _mres = new ManualResetEventSlim();

private ConcurrencyLimiterMiddleware _middlewareQueue;
private ConcurrencyLimiterMiddleware _middlewareStack;

[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();
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,7 @@ public ConcurrencyLimiterOptions() { }
public partial interface IQueuePolicy
{
void OnExit();
System.Threading.Tasks.Task<bool> TryEnterAsync();
System.Threading.Tasks.ValueTask<bool> TryEnterAsync();
}
public partial class QueuePolicyOptions
{
Expand All @@ -37,7 +37,7 @@ namespace Microsoft.Extensions.DependencyInjection
{
public static partial class QueuePolicyServiceCollectionExtensions
{
public static Microsoft.Extensions.DependencyInjection.IServiceCollection AddFIFOQueue(this Microsoft.Extensions.DependencyInjection.IServiceCollection services, System.Action<Microsoft.AspNetCore.ConcurrencyLimiter.QueuePolicyOptions> configure) { throw null; }
public static Microsoft.Extensions.DependencyInjection.IServiceCollection AddLIFOQueue(this Microsoft.Extensions.DependencyInjection.IServiceCollection services, System.Action<Microsoft.AspNetCore.ConcurrencyLimiter.QueuePolicyOptions> configure) { throw null; }
public static Microsoft.Extensions.DependencyInjection.IServiceCollection AddQueuePolicy(this Microsoft.Extensions.DependencyInjection.IServiceCollection services, System.Action<Microsoft.AspNetCore.ConcurrencyLimiter.QueuePolicyOptions> configure) { throw null; }
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This would be next to other stuff unrelated to to the ConcurrencyLimiter like collection.AddMvc(). I think this will make it confusing to most developers what this is adding a QueuePolicy to. Maybe this should be AddConcurrencyLimiterQueuePolicy() or AddConcurrencyLimiterStackPolicy().

I don't want to hold up the PR on this though. We can have a bigger discussion over naming and do a follow-up PR.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Follow up PR sounds good

public static Microsoft.Extensions.DependencyInjection.IServiceCollection AddStackPolicy(this Microsoft.Extensions.DependencyInjection.IServiceCollection services, System.Action<Microsoft.AspNetCore.ConcurrencyLimiter.QueuePolicyOptions> configure) { throw null; }
}
}
13 changes: 5 additions & 8 deletions src/Middleware/ConcurrencyLimiter/sample/Startup.cs
Original file line number Diff line number Diff line change
@@ -1,8 +1,6 @@
// Copyright (c) .NET Foundation. All rights reserved.
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.

using System;
using System.IO;
using System.Threading.Tasks;
using Microsoft.AspNetCore.Builder;
using Microsoft.AspNetCore.Hosting;
Expand All @@ -17,9 +15,10 @@ public class Startup
{
public void ConfigureServices(IServiceCollection services)
{
services.AddLIFOQueue((options) => {
options.MaxConcurrentRequests = Environment.ProcessorCount;
options.RequestQueueLimit = 50;
services.AddStackPolicy(options =>
{
options.MaxConcurrentRequests = 2;
options.RequestQueueLimit = 25;
});
}

Expand All @@ -28,8 +27,7 @@ public void Configure(IApplicationBuilder app, ILoggerFactory loggerFactory)
app.UseConcurrencyLimiter();
app.Run(async context =>
{
var delay = 100;
Task.Delay(delay).Wait();
Task.Delay(100).Wait(); // 100ms sync-over-async

await context.Response.WriteAsync("Hello World!");
});
Expand All @@ -39,7 +37,6 @@ public static void Main(string[] args)
{
new WebHostBuilder()
.UseKestrel()
.UseContentRoot(Directory.GetCurrentDirectory()) // for cert file
.UseStartup<Startup>()
.Build()
.Run();
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,9 +2,7 @@
// 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.Diagnostics.Tracing;
using System.Text;
using System.Threading;
using Microsoft.Extensions.Internal;

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -48,19 +48,23 @@ 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())
{
await waitInQueueTask;
result = await waitInQueueTask;
}
}

if (waitInQueueTask.Result)
if (result)
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Would any unit tests now fail if you were to check waitInQueueTask.Result here?

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Bit confused here. I thought the point of this change was to avoid calling GetResult twice. Wouldn't checking waitInQueueTask.Result here run into the same issue?

{
try
{
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@ public interface IQueuePolicy
/// When it returns 'true' the request procedes to the server.
/// When it returns 'false' the request is rejected immediately.
/// </summary>
Task<bool> TryEnterAsync();
ValueTask<bool> TryEnterAsync();

/// <summary>
/// Called after successful requests have been returned from the server.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@

namespace Microsoft.AspNetCore.ConcurrencyLimiter
{
internal class FIFOQueuePolicy : IQueuePolicy, IDisposable
internal class QueuePolicy : IQueuePolicy, IDisposable
{
private readonly int _maxConcurrentRequests;
private readonly int _requestQueueLimit;
Expand All @@ -17,7 +17,7 @@ internal class FIFOQueuePolicy : IQueuePolicy, IDisposable
private object _totalRequestsLock = new object();
public int TotalRequests { get; private set; }

public FIFOQueuePolicy(IOptions<QueuePolicyOptions> options)
public QueuePolicy(IOptions<QueuePolicyOptions> options)
{
_maxConcurrentRequests = options.Value.MaxConcurrentRequests;
if (_maxConcurrentRequests <= 0)
Expand All @@ -34,7 +34,7 @@ public FIFOQueuePolicy(IOptions<QueuePolicyOptions> options)
_serverSemaphore = new SemaphoreSlim(_maxConcurrentRequests);
}

public async Task<bool> TryEnterAsync()
public async ValueTask<bool> TryEnterAsync()
{
// a return value of 'false' indicates that the request is rejected
// a return value of 'true' indicates that the request may proceed
Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,5 @@
using System;
using System.Collections.Generic;
using System.Text;
// 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.ConcurrencyLimiter
{
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -18,10 +18,10 @@ public static class QueuePolicyServiceCollectionExtensions
/// <param name="configure">Set the options used by the queue.
/// Mandatory, since <see cref="QueuePolicyOptions.MaxConcurrentRequests"></see> must be provided.</param>
/// <returns></returns>
public static IServiceCollection AddFIFOQueue(this IServiceCollection services, Action<QueuePolicyOptions> configure)
public static IServiceCollection AddQueuePolicy(this IServiceCollection services, Action<QueuePolicyOptions> configure)
{
services.Configure(configure);
services.AddSingleton<IQueuePolicy, FIFOQueuePolicy>();
services.AddSingleton<IQueuePolicy, QueuePolicy>();
return services;
}

Expand All @@ -32,10 +32,10 @@ public static IServiceCollection AddFIFOQueue(this IServiceCollection services,
/// <param name="configure">Set the options used by the queue.
/// Mandatory, since <see cref="QueuePolicyOptions.MaxConcurrentRequests"></see> must be provided.</param>
/// <returns></returns>
public static IServiceCollection AddLIFOQueue(this IServiceCollection services, Action<QueuePolicyOptions> configure)
public static IServiceCollection AddStackPolicy(this IServiceCollection services, Action<QueuePolicyOptions> configure)
{
services.Configure(configure);
services.AddSingleton<IQueuePolicy, LIFOQueuePolicy>();
services.AddSingleton<IQueuePolicy, StackPolicy>();
return services;
}
}
Expand Down
Loading