Remove QueuePolicy locks (#23187)

* Use field rather than property

Operate on a field directly, rather than through a property.

* Make field readonly

Make field read-only as its value is never changed.

* Remove lock

Use Interlocked.Decrement() instead of taking a lock.

* Remove lock for increment

This removes the need for the lock in the success case at the cost of an extra  Interlocked.Decrement() call for the failed case.

* Fix typos

Change "availible" to "available".

* Add unit test for full queue

Add a unit test that validates request is not queued if the queue is already full.
This commit is contained in:
Martin Costello 2020-06-30 06:11:58 +01:00 committed by GitHub
parent 1c27ba1bbd
commit acabbbc61b
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
4 changed files with 33 additions and 17 deletions

View File

@ -96,7 +96,7 @@ namespace Microsoft.AspNetCore.ConcurrencyLimiter
LoggerMessage.Define<int>(LogLevel.Debug, new EventId(3, "RequestRunImmediately"), "Below MaxConcurrentRequests limit, running request immediately. Current active requests: {ActiveRequests}");
private static readonly Action<ILogger, Exception> _requestRejectedQueueFull =
LoggerMessage.Define(LogLevel.Debug, new EventId(4, "RequestRejectedQueueFull"), "Currently at the 'RequestQueueLimit', rejecting this request with a '503 server not availible' error");
LoggerMessage.Define(LogLevel.Debug, new EventId(4, "RequestRejectedQueueFull"), "Currently at the 'RequestQueueLimit', rejecting this request with a '503 server not available' error");
internal static void RequestEnqueued(ILogger logger, int activeRequests)
{

View File

@ -13,9 +13,9 @@ namespace Microsoft.AspNetCore.ConcurrencyLimiter
private readonly int _maxTotalRequest;
private readonly SemaphoreSlim _serverSemaphore;
private object _totalRequestsLock = new object();
private int _totalRequests;
public int TotalRequests { get; private set; }
public int TotalRequests => _totalRequests;
public QueuePolicy(IOptions<QueuePolicyOptions> options)
{
@ -44,14 +44,12 @@ namespace Microsoft.AspNetCore.ConcurrencyLimiter
// a return value of 'true' indicates that the request may proceed
// _serverSemaphore.Release is *not* called in this method, it is called externally when requests leave the server
lock (_totalRequestsLock)
{
if (TotalRequests >= _maxTotalRequest)
{
return new ValueTask<bool>(false);
}
int totalRequests = Interlocked.Increment(ref _totalRequests);
TotalRequests++;
if (totalRequests > _maxTotalRequest)
{
Interlocked.Decrement(ref _totalRequests);
return new ValueTask<bool>(false);
}
Task task = _serverSemaphore.WaitAsync();
@ -67,10 +65,7 @@ namespace Microsoft.AspNetCore.ConcurrencyLimiter
{
_serverSemaphore.Release();
lock (_totalRequestsLock)
{
TotalRequests--;
}
Interlocked.Decrement(ref _totalRequests);
}
public void Dispose()

View File

@ -15,7 +15,7 @@ namespace Microsoft.AspNetCore.ConcurrencyLimiter
public int MaxConcurrentRequests { get; set; }
/// <summary>
/// Maximum number of queued requests before the server starts rejecting connections with '503 Service Unavailible'.
/// 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.
/// </summary>
public int RequestQueueLimit { get; set; }

View File

@ -9,7 +9,7 @@ namespace Microsoft.AspNetCore.ConcurrencyLimiter.Tests.PolicyTests
public class QueuePolicyTests
{
[Fact]
public void DoesNotWaitIfSpaceAvailible()
public void DoesNotWaitIfSpaceAvailable()
{
using var s = TestUtils.CreateQueuePolicy(2);
@ -24,7 +24,7 @@ namespace Microsoft.AspNetCore.ConcurrencyLimiter.Tests.PolicyTests
}
[Fact]
public async Task WaitsIfNoSpaceAvailible()
public async Task WaitsIfNoSpaceAvailable()
{
using var s = TestUtils.CreateQueuePolicy(1);
Assert.True(await s.TryEnterAsync().OrTimeout());
@ -36,6 +36,27 @@ namespace Microsoft.AspNetCore.ConcurrencyLimiter.Tests.PolicyTests
Assert.True(await waitingTask.OrTimeout());
}
[Fact]
public void DoesNotWaitIfQueueFull()
{
using var s = TestUtils.CreateQueuePolicy(2, 1);
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);
}
[Fact]
public async Task IsEncapsulated()
{