Skip to content

Commit acabbbc

Browse files
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.
1 parent 1c27ba1 commit acabbbc

File tree

4 files changed

+33
-17
lines changed

4 files changed

+33
-17
lines changed

src/Middleware/ConcurrencyLimiter/src/ConcurrencyLimiterMiddleware.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -96,7 +96,7 @@ private static class ConcurrencyLimiterLog
9696
LoggerMessage.Define<int>(LogLevel.Debug, new EventId(3, "RequestRunImmediately"), "Below MaxConcurrentRequests limit, running request immediately. Current active requests: {ActiveRequests}");
9797

9898
private static readonly Action<ILogger, Exception> _requestRejectedQueueFull =
99-
LoggerMessage.Define(LogLevel.Debug, new EventId(4, "RequestRejectedQueueFull"), "Currently at the 'RequestQueueLimit', rejecting this request with a '503 server not availible' error");
99+
LoggerMessage.Define(LogLevel.Debug, new EventId(4, "RequestRejectedQueueFull"), "Currently at the 'RequestQueueLimit', rejecting this request with a '503 server not available' error");
100100

101101
internal static void RequestEnqueued(ILogger logger, int activeRequests)
102102
{

src/Middleware/ConcurrencyLimiter/src/QueuePolicies/QueuePolicy.cs

Lines changed: 8 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -13,9 +13,9 @@ internal class QueuePolicy : IQueuePolicy, IDisposable
1313
private readonly int _maxTotalRequest;
1414
private readonly SemaphoreSlim _serverSemaphore;
1515

16-
private object _totalRequestsLock = new object();
16+
private int _totalRequests;
1717

18-
public int TotalRequests { get; private set; }
18+
public int TotalRequests => _totalRequests;
1919

2020
public QueuePolicy(IOptions<QueuePolicyOptions> options)
2121
{
@@ -44,14 +44,12 @@ public ValueTask<bool> TryEnterAsync()
4444
// a return value of 'true' indicates that the request may proceed
4545
// _serverSemaphore.Release is *not* called in this method, it is called externally when requests leave the server
4646

47-
lock (_totalRequestsLock)
48-
{
49-
if (TotalRequests >= _maxTotalRequest)
50-
{
51-
return new ValueTask<bool>(false);
52-
}
47+
int totalRequests = Interlocked.Increment(ref _totalRequests);
5348

54-
TotalRequests++;
49+
if (totalRequests > _maxTotalRequest)
50+
{
51+
Interlocked.Decrement(ref _totalRequests);
52+
return new ValueTask<bool>(false);
5553
}
5654

5755
Task task = _serverSemaphore.WaitAsync();
@@ -67,10 +65,7 @@ public void OnExit()
6765
{
6866
_serverSemaphore.Release();
6967

70-
lock (_totalRequestsLock)
71-
{
72-
TotalRequests--;
73-
}
68+
Interlocked.Decrement(ref _totalRequests);
7469
}
7570

7671
public void Dispose()

src/Middleware/ConcurrencyLimiter/src/QueuePolicies/QueuePolicyOptions.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,7 @@ public class QueuePolicyOptions
1515
public int MaxConcurrentRequests { get; set; }
1616

1717
/// <summary>
18-
/// Maximum number of queued requests before the server starts rejecting connections with '503 Service Unavailible'.
18+
/// Maximum number of queued requests before the server starts rejecting connections with '503 Service Unavailable'.
1919
/// This option is highly application dependant, and must be configured by the application.
2020
/// </summary>
2121
public int RequestQueueLimit { get; set; }

src/Middleware/ConcurrencyLimiter/test/PolicyTests/QueuePolicyTests.cs

Lines changed: 23 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,7 @@ namespace Microsoft.AspNetCore.ConcurrencyLimiter.Tests.PolicyTests
99
public class QueuePolicyTests
1010
{
1111
[Fact]
12-
public void DoesNotWaitIfSpaceAvailible()
12+
public void DoesNotWaitIfSpaceAvailable()
1313
{
1414
using var s = TestUtils.CreateQueuePolicy(2);
1515

@@ -24,7 +24,7 @@ public void DoesNotWaitIfSpaceAvailible()
2424
}
2525

2626
[Fact]
27-
public async Task WaitsIfNoSpaceAvailible()
27+
public async Task WaitsIfNoSpaceAvailable()
2828
{
2929
using var s = TestUtils.CreateQueuePolicy(1);
3030
Assert.True(await s.TryEnterAsync().OrTimeout());
@@ -36,6 +36,27 @@ public async Task WaitsIfNoSpaceAvailible()
3636
Assert.True(await waitingTask.OrTimeout());
3737
}
3838

39+
[Fact]
40+
public void DoesNotWaitIfQueueFull()
41+
{
42+
using var s = TestUtils.CreateQueuePolicy(2, 1);
43+
44+
var t1 = s.TryEnterAsync();
45+
Assert.True(t1.IsCompleted);
46+
Assert.True(t1.Result);
47+
48+
var t2 = s.TryEnterAsync();
49+
Assert.True(t2.IsCompleted);
50+
Assert.True(t2.Result);
51+
52+
var t3 = s.TryEnterAsync();
53+
Assert.False(t3.IsCompleted);
54+
55+
var t4 = s.TryEnterAsync();
56+
Assert.True(t4.IsCompleted);
57+
Assert.False(t4.Result);
58+
}
59+
3960
[Fact]
4061
public async Task IsEncapsulated()
4162
{

0 commit comments

Comments
 (0)