parent
edf8bbe65d
commit
f9aa85a829
|
|
@ -13,7 +13,8 @@ namespace Microsoft.AspNetCore.RequestThrottling.Microbenchmarks
|
||||||
{
|
{
|
||||||
private const int _numRequests = 20000;
|
private const int _numRequests = 20000;
|
||||||
|
|
||||||
private RequestThrottlingMiddleware _middleware;
|
private RequestThrottlingMiddleware _middlewareFIFO;
|
||||||
|
private RequestThrottlingMiddleware _middlewareLIFO;
|
||||||
private RequestDelegate _restOfServer;
|
private RequestDelegate _restOfServer;
|
||||||
|
|
||||||
[GlobalSetup]
|
[GlobalSetup]
|
||||||
|
|
@ -21,9 +22,14 @@ namespace Microsoft.AspNetCore.RequestThrottling.Microbenchmarks
|
||||||
{
|
{
|
||||||
_restOfServer = YieldsThreadInternally ? (RequestDelegate)YieldsThread : (RequestDelegate)CompletesImmediately;
|
_restOfServer = YieldsThreadInternally ? (RequestDelegate)YieldsThread : (RequestDelegate)CompletesImmediately;
|
||||||
|
|
||||||
_middleware = TestUtils.CreateTestMiddleware_TailDrop(
|
_middlewareFIFO = TestUtils.CreateTestMiddleware_TailDrop(
|
||||||
maxConcurrentRequests: 1,
|
maxConcurrentRequests: 1,
|
||||||
requestQueueLimit: 0,
|
requestQueueLimit: 100,
|
||||||
|
next: _restOfServer);
|
||||||
|
|
||||||
|
_middlewareLIFO = TestUtils.CreateTestMiddleware_StackPolicy(
|
||||||
|
maxConcurrentRequests: 1,
|
||||||
|
requestQueueLimit: 100,
|
||||||
next: _restOfServer);
|
next: _restOfServer);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -40,11 +46,20 @@ namespace Microsoft.AspNetCore.RequestThrottling.Microbenchmarks
|
||||||
}
|
}
|
||||||
|
|
||||||
[Benchmark(OperationsPerInvoke = _numRequests)]
|
[Benchmark(OperationsPerInvoke = _numRequests)]
|
||||||
public async Task WithEmptyQueueOverhead()
|
public async Task WithEmptyQueueOverhead_FIFO()
|
||||||
{
|
{
|
||||||
for (int i = 0; i < _numRequests; i++)
|
for (int i = 0; i < _numRequests; i++)
|
||||||
{
|
{
|
||||||
await _middleware.Invoke(null);
|
await _middlewareFIFO.Invoke(null);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
[Benchmark(OperationsPerInvoke = _numRequests)]
|
||||||
|
public async Task WithEmptyQueueOverhead_LIFO()
|
||||||
|
{
|
||||||
|
for (int i = 0; i < _numRequests; i++)
|
||||||
|
{
|
||||||
|
await _middlewareLIFO.Invoke(null);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -11,11 +11,12 @@ namespace Microsoft.AspNetCore.RequestThrottling.Microbenchmarks
|
||||||
{
|
{
|
||||||
public class QueueFullOverhead
|
public class QueueFullOverhead
|
||||||
{
|
{
|
||||||
private const int _numRequests = 2000;
|
private const int _numRequests = 200;
|
||||||
private int _requestCount = 0;
|
private int _requestCount = 0;
|
||||||
private ManualResetEventSlim _mres = new ManualResetEventSlim();
|
private ManualResetEventSlim _mres = new ManualResetEventSlim();
|
||||||
|
|
||||||
private RequestThrottlingMiddleware _middleware;
|
private RequestThrottlingMiddleware _middleware_FIFO;
|
||||||
|
private RequestThrottlingMiddleware _middleware_LIFO;
|
||||||
|
|
||||||
[Params(8)]
|
[Params(8)]
|
||||||
public int MaxConcurrentRequests;
|
public int MaxConcurrentRequests;
|
||||||
|
|
@ -23,11 +24,15 @@ namespace Microsoft.AspNetCore.RequestThrottling.Microbenchmarks
|
||||||
[GlobalSetup]
|
[GlobalSetup]
|
||||||
public void GlobalSetup()
|
public void GlobalSetup()
|
||||||
{
|
{
|
||||||
_middleware = TestUtils.CreateTestMiddleware_TailDrop(
|
_middleware_FIFO = TestUtils.CreateTestMiddleware_TailDrop(
|
||||||
maxConcurrentRequests: MaxConcurrentRequests,
|
maxConcurrentRequests: MaxConcurrentRequests,
|
||||||
requestQueueLimit: _numRequests,
|
requestQueueLimit: _numRequests,
|
||||||
next: IncrementAndCheck
|
next: IncrementAndCheck);
|
||||||
);
|
|
||||||
|
_middleware_LIFO = TestUtils.CreateTestMiddleware_StackPolicy(
|
||||||
|
maxConcurrentRequests: MaxConcurrentRequests,
|
||||||
|
requestQueueLimit: _numRequests,
|
||||||
|
next: IncrementAndCheck);
|
||||||
}
|
}
|
||||||
|
|
||||||
[IterationSetup]
|
[IterationSetup]
|
||||||
|
|
@ -59,14 +64,26 @@ namespace Microsoft.AspNetCore.RequestThrottling.Microbenchmarks
|
||||||
}
|
}
|
||||||
|
|
||||||
[Benchmark(OperationsPerInvoke = _numRequests)]
|
[Benchmark(OperationsPerInvoke = _numRequests)]
|
||||||
public void QueueingAll()
|
public void QueueingAll_FIFO()
|
||||||
{
|
{
|
||||||
for (int i = 0; i < _numRequests; i++)
|
for (int i = 0; i < _numRequests; i++)
|
||||||
{
|
{
|
||||||
_ = _middleware.Invoke(null);
|
_ = _middleware_FIFO.Invoke(null);
|
||||||
}
|
}
|
||||||
|
|
||||||
_mres.Wait();
|
_mres.Wait();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
[Benchmark(OperationsPerInvoke = _numRequests)]
|
||||||
|
public void QueueingAll_LIFO()
|
||||||
|
{
|
||||||
|
for (int i = 0; i < _numRequests; i++)
|
||||||
|
{
|
||||||
|
_ = _middleware_LIFO.Invoke(null);
|
||||||
|
}
|
||||||
|
|
||||||
|
_mres.Wait();
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -28,11 +28,11 @@ namespace Microsoft.AspNetCore.RequestThrottling
|
||||||
public Microsoft.AspNetCore.Http.RequestDelegate OnRejected { [System.Runtime.CompilerServices.CompilerGeneratedAttribute]get { throw null; } [System.Runtime.CompilerServices.CompilerGeneratedAttribute]set { } }
|
public Microsoft.AspNetCore.Http.RequestDelegate OnRejected { [System.Runtime.CompilerServices.CompilerGeneratedAttribute]get { throw null; } [System.Runtime.CompilerServices.CompilerGeneratedAttribute]set { } }
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
namespace Microsoft.AspNetCore.RequestThrottling.Policies
|
namespace Microsoft.AspNetCore.RequestThrottling.QueuePolicies
|
||||||
{
|
{
|
||||||
public partial class TailDropOptions
|
public partial class QueuePolicyOptions
|
||||||
{
|
{
|
||||||
public TailDropOptions() { }
|
public QueuePolicyOptions() { }
|
||||||
public int MaxConcurrentRequests { [System.Runtime.CompilerServices.CompilerGeneratedAttribute]get { throw null; } [System.Runtime.CompilerServices.CompilerGeneratedAttribute]set { } }
|
public int MaxConcurrentRequests { [System.Runtime.CompilerServices.CompilerGeneratedAttribute]get { throw null; } [System.Runtime.CompilerServices.CompilerGeneratedAttribute]set { } }
|
||||||
public int RequestQueueLimit { [System.Runtime.CompilerServices.CompilerGeneratedAttribute]get { throw null; } [System.Runtime.CompilerServices.CompilerGeneratedAttribute]set { } }
|
public int RequestQueueLimit { [System.Runtime.CompilerServices.CompilerGeneratedAttribute]get { throw null; } [System.Runtime.CompilerServices.CompilerGeneratedAttribute]set { } }
|
||||||
}
|
}
|
||||||
|
|
@ -41,6 +41,7 @@ namespace Microsoft.Extensions.DependencyInjection
|
||||||
{
|
{
|
||||||
public static partial class QueuePolicyServiceCollectionExtensions
|
public static partial class QueuePolicyServiceCollectionExtensions
|
||||||
{
|
{
|
||||||
public static Microsoft.Extensions.DependencyInjection.IServiceCollection AddTailDropQueue(this Microsoft.Extensions.DependencyInjection.IServiceCollection services, System.Action<Microsoft.AspNetCore.RequestThrottling.Policies.TailDropOptions> configure) { throw null; }
|
public static Microsoft.Extensions.DependencyInjection.IServiceCollection AddStackQueue(this Microsoft.Extensions.DependencyInjection.IServiceCollection services, System.Action<Microsoft.AspNetCore.RequestThrottling.QueuePolicies.QueuePolicyOptions> configure) { throw null; }
|
||||||
|
public static Microsoft.Extensions.DependencyInjection.IServiceCollection AddTailDropQueue(this Microsoft.Extensions.DependencyInjection.IServiceCollection services, System.Action<Microsoft.AspNetCore.RequestThrottling.QueuePolicies.QueuePolicyOptions> configure) { throw null; }
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -8,6 +8,7 @@
|
||||||
<Reference Include="Microsoft.Extensions.Logging.Console" />
|
<Reference Include="Microsoft.Extensions.Logging.Console" />
|
||||||
<Reference Include="Microsoft.AspNetCore.RequestThrottling" />
|
<Reference Include="Microsoft.AspNetCore.RequestThrottling" />
|
||||||
<Reference Include="Microsoft.AspNetCore.Server.Kestrel" />
|
<Reference Include="Microsoft.AspNetCore.Server.Kestrel" />
|
||||||
|
<Reference Include="Microsoft.Extensions.Configuration.CommandLine" />
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
|
|
||||||
<ItemGroup Condition="'$(BenchmarksTargetFramework)' != ''">
|
<ItemGroup Condition="'$(BenchmarksTargetFramework)' != ''">
|
||||||
|
|
|
||||||
|
|
@ -1,11 +1,13 @@
|
||||||
// Copyright (c) .NET Foundation. All rights reserved.
|
// 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.
|
// 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.IO;
|
||||||
using System.Threading.Tasks;
|
using System.Threading.Tasks;
|
||||||
using Microsoft.AspNetCore.Builder;
|
using Microsoft.AspNetCore.Builder;
|
||||||
using Microsoft.AspNetCore.Hosting;
|
using Microsoft.AspNetCore.Hosting;
|
||||||
using Microsoft.AspNetCore.Http;
|
using Microsoft.AspNetCore.Http;
|
||||||
|
using Microsoft.Extensions.Configuration;
|
||||||
using Microsoft.Extensions.DependencyInjection;
|
using Microsoft.Extensions.DependencyInjection;
|
||||||
using Microsoft.Extensions.Hosting;
|
using Microsoft.Extensions.Hosting;
|
||||||
using Microsoft.Extensions.Logging;
|
using Microsoft.Extensions.Logging;
|
||||||
|
|
@ -14,14 +16,16 @@ namespace RequestThrottlingSample
|
||||||
{
|
{
|
||||||
public class Startup
|
public class Startup
|
||||||
{
|
{
|
||||||
|
static IConfiguration _config;
|
||||||
|
|
||||||
// This method gets called by the runtime. Use this method to add services to the container.
|
// This method gets called by the runtime. Use this method to add services to the container.
|
||||||
// For more information on how to configure your application, visit https://go.microsoft.com/fwlink/?LinkID=398940
|
// For more information on how to configure your application, visit https://go.microsoft.com/fwlink/?LinkID=398940
|
||||||
public void ConfigureServices(IServiceCollection services)
|
public void ConfigureServices(IServiceCollection services)
|
||||||
{
|
{
|
||||||
services.AddTailDropQueue((options) =>
|
services.AddTailDropQueue((options) =>
|
||||||
{
|
{
|
||||||
options.MaxConcurrentRequests = 4;
|
options.MaxConcurrentRequests = Math.Max(1, _config.GetValue<int>("maxCores"));
|
||||||
options.RequestQueueLimit = 0;
|
options.RequestQueueLimit = Math.Max(1, _config.GetValue<int>("maxQueue"));
|
||||||
});
|
});
|
||||||
|
|
||||||
services.AddLogging();
|
services.AddLogging();
|
||||||
|
|
@ -30,17 +34,21 @@ namespace RequestThrottlingSample
|
||||||
public void Configure(IApplicationBuilder app, ILoggerFactory loggerFactory)
|
public void Configure(IApplicationBuilder app, ILoggerFactory loggerFactory)
|
||||||
{
|
{
|
||||||
app.UseRequestThrottling();
|
app.UseRequestThrottling();
|
||||||
|
|
||||||
app.Run(async context =>
|
app.Run(async context =>
|
||||||
{
|
{
|
||||||
await context.Response.WriteAsync("Hello Request Throttling! If you refresh this page a bunch, it will 503.");
|
await context.Response.WriteAsync("Hello Request Throttling! If you rapidly refresh this page, it will 503.");
|
||||||
await Task.Delay(1000);
|
await Task.Delay(400);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
// Entry point for the application.
|
// Entry point for the application.
|
||||||
public static void Main(string[] args)
|
public static void Main(string[] args)
|
||||||
{
|
{
|
||||||
|
_config = new ConfigurationBuilder()
|
||||||
|
.AddEnvironmentVariables(prefix: "ASPNETCORE_")
|
||||||
|
.AddCommandLine(args)
|
||||||
|
.Build();
|
||||||
|
|
||||||
var host = new WebHostBuilder()
|
var host = new WebHostBuilder()
|
||||||
.UseKestrel()
|
.UseKestrel()
|
||||||
.UseContentRoot(Directory.GetCurrentDirectory()) // for the cert file
|
.UseContentRoot(Directory.GetCurrentDirectory()) // for the cert file
|
||||||
|
|
|
||||||
|
|
@ -1,34 +0,0 @@
|
||||||
// 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.ComponentModel;
|
|
||||||
using System.Text;
|
|
||||||
using System.Xml.Schema;
|
|
||||||
using Microsoft.AspNetCore.RequestThrottling;
|
|
||||||
using Microsoft.AspNetCore.RequestThrottling.Policies;
|
|
||||||
using Microsoft.Extensions.DependencyInjection.Extensions;
|
|
||||||
|
|
||||||
namespace Microsoft.Extensions.DependencyInjection
|
|
||||||
{
|
|
||||||
/// <summary>
|
|
||||||
/// Contains methods for adding Q
|
|
||||||
/// </summary>
|
|
||||||
public static class QueuePolicyServiceCollectionExtensions
|
|
||||||
{
|
|
||||||
/// <summary>
|
|
||||||
/// Tells <see cref="RequestThrottlingMiddleware"/> to use a TailDrop queue as its queueing strategy.
|
|
||||||
/// </summary>
|
|
||||||
/// <param name="services">The <see cref="IServiceCollection"/> to add services to.</param>
|
|
||||||
/// <param name="configure">Set the options used by the queue.
|
|
||||||
/// Mandatory, since <see cref="TailDropOptions.MaxConcurrentRequests"></see> must be provided.</param>
|
|
||||||
/// <returns></returns>
|
|
||||||
public static IServiceCollection AddTailDropQueue(this IServiceCollection services, Action<TailDropOptions> configure)
|
|
||||||
{
|
|
||||||
services.Configure<TailDropOptions>(configure);
|
|
||||||
services.AddSingleton<IQueuePolicy, TailDrop>();
|
|
||||||
return services;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
@ -2,12 +2,12 @@ using System;
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
using System.Text;
|
using System.Text;
|
||||||
|
|
||||||
namespace Microsoft.AspNetCore.RequestThrottling.Policies
|
namespace Microsoft.AspNetCore.RequestThrottling.QueuePolicies
|
||||||
{
|
{
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Specifies options for the <see cref="TailDrop"/>
|
/// Specifies options for the <see cref="IQueuePolicy"/>
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public class TailDropOptions
|
public class QueuePolicyOptions
|
||||||
{
|
{
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Maximum number of concurrent requests. Any extras will be queued on the server.
|
/// Maximum number of concurrent requests. Any extras will be queued on the server.
|
||||||
|
|
@ -0,0 +1,43 @@
|
||||||
|
// 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 Microsoft.AspNetCore.RequestThrottling;
|
||||||
|
using Microsoft.AspNetCore.RequestThrottling.QueuePolicies;
|
||||||
|
|
||||||
|
namespace Microsoft.Extensions.DependencyInjection
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Contains methods for specifying which queue the middleware should use.
|
||||||
|
/// </summary>
|
||||||
|
public static class QueuePolicyServiceCollectionExtensions
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Tells <see cref="RequestThrottlingMiddleware"/> to use a FIFO queue as its queueing strategy.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="services">The <see cref="IServiceCollection"/> to add services to.</param>
|
||||||
|
/// <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 AddTailDropQueue(this IServiceCollection services, Action<QueuePolicyOptions> configure)
|
||||||
|
{
|
||||||
|
services.Configure(configure);
|
||||||
|
services.AddSingleton<IQueuePolicy, TailDropQueuePolicy>();
|
||||||
|
return services;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Tells <see cref="RequestThrottlingMiddleware"/> to use a LIFO stack as its queueing strategy.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="services">The <see cref="IServiceCollection"/> to add services to.</param>
|
||||||
|
/// <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 AddStackQueue(this IServiceCollection services, Action<QueuePolicyOptions> configure)
|
||||||
|
{
|
||||||
|
services.Configure(configure);
|
||||||
|
services.AddSingleton<IQueuePolicy, StackQueuePolicy>();
|
||||||
|
return services;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,104 @@
|
||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Threading.Tasks;
|
||||||
|
using Microsoft.Extensions.Options;
|
||||||
|
|
||||||
|
namespace Microsoft.AspNetCore.RequestThrottling.QueuePolicies
|
||||||
|
{
|
||||||
|
internal class StackQueuePolicy : IQueuePolicy
|
||||||
|
{
|
||||||
|
private readonly List<TaskCompletionSource<bool>> _buffer;
|
||||||
|
private readonly int _maxQueueCapacity;
|
||||||
|
private readonly int _maxConcurrentRequests;
|
||||||
|
private bool _hasReachedCapacity;
|
||||||
|
private int _head;
|
||||||
|
private int _queueLength;
|
||||||
|
|
||||||
|
private static readonly Task<bool> _trueTask = Task.FromResult(true);
|
||||||
|
|
||||||
|
private readonly object _bufferLock = new Object();
|
||||||
|
|
||||||
|
private int _freeServerSpots;
|
||||||
|
|
||||||
|
public StackQueuePolicy(IOptions<QueuePolicyOptions> options)
|
||||||
|
{
|
||||||
|
_buffer = new List<TaskCompletionSource<bool>>();
|
||||||
|
_maxQueueCapacity = options.Value.RequestQueueLimit;
|
||||||
|
_maxConcurrentRequests = options.Value.MaxConcurrentRequests;
|
||||||
|
_freeServerSpots = options.Value.MaxConcurrentRequests;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Task<bool> TryEnterAsync()
|
||||||
|
{
|
||||||
|
lock (_bufferLock)
|
||||||
|
{
|
||||||
|
if (_freeServerSpots > 0)
|
||||||
|
{
|
||||||
|
_freeServerSpots--;
|
||||||
|
return _trueTask;
|
||||||
|
}
|
||||||
|
|
||||||
|
// if queue is full, cancel oldest request
|
||||||
|
if (_queueLength == _maxQueueCapacity)
|
||||||
|
{
|
||||||
|
_hasReachedCapacity = true;
|
||||||
|
_buffer[_head].SetResult(false);
|
||||||
|
_queueLength--;
|
||||||
|
}
|
||||||
|
|
||||||
|
// enqueue request with a tcs
|
||||||
|
var tcs = new TaskCompletionSource<bool>(TaskCreationOptions.RunContinuationsAsynchronously);
|
||||||
|
if (_hasReachedCapacity || _queueLength < _buffer.Count)
|
||||||
|
{
|
||||||
|
_buffer[_head] = tcs;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
_buffer.Add(tcs);
|
||||||
|
}
|
||||||
|
_queueLength++;
|
||||||
|
|
||||||
|
// increment _head for next time
|
||||||
|
_head++;
|
||||||
|
if (_head == _maxQueueCapacity)
|
||||||
|
{
|
||||||
|
_head = 0;
|
||||||
|
}
|
||||||
|
return tcs.Task;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public void OnExit()
|
||||||
|
{
|
||||||
|
lock (_bufferLock)
|
||||||
|
{
|
||||||
|
if (_queueLength == 0)
|
||||||
|
{
|
||||||
|
_freeServerSpots++;
|
||||||
|
|
||||||
|
if (_freeServerSpots > _maxConcurrentRequests)
|
||||||
|
{
|
||||||
|
_freeServerSpots--;
|
||||||
|
throw new InvalidOperationException("OnExit must only be called once per successful call to TryEnterAsync");
|
||||||
|
}
|
||||||
|
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// step backwards and launch a new task
|
||||||
|
if (_head == 0)
|
||||||
|
{
|
||||||
|
_head = _maxQueueCapacity - 1;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
_head--;
|
||||||
|
}
|
||||||
|
|
||||||
|
_buffer[_head].SetResult(true);
|
||||||
|
_buffer[_head] = null;
|
||||||
|
_queueLength--;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -6,9 +6,9 @@ using System.Threading;
|
||||||
using System.Threading.Tasks;
|
using System.Threading.Tasks;
|
||||||
using Microsoft.Extensions.Options;
|
using Microsoft.Extensions.Options;
|
||||||
|
|
||||||
namespace Microsoft.AspNetCore.RequestThrottling.Policies
|
namespace Microsoft.AspNetCore.RequestThrottling.QueuePolicies
|
||||||
{
|
{
|
||||||
internal class TailDrop : IQueuePolicy, IDisposable
|
internal class TailDropQueuePolicy : IQueuePolicy, IDisposable
|
||||||
{
|
{
|
||||||
private readonly int _maxConcurrentRequests;
|
private readonly int _maxConcurrentRequests;
|
||||||
private readonly int _requestQueueLimit;
|
private readonly int _requestQueueLimit;
|
||||||
|
|
@ -17,7 +17,7 @@ namespace Microsoft.AspNetCore.RequestThrottling.Policies
|
||||||
private object _totalRequestsLock = new object();
|
private object _totalRequestsLock = new object();
|
||||||
public int TotalRequests { get; private set; }
|
public int TotalRequests { get; private set; }
|
||||||
|
|
||||||
public TailDrop(IOptions<TailDropOptions> options)
|
public TailDropQueuePolicy(IOptions<QueuePolicyOptions> options)
|
||||||
{
|
{
|
||||||
_maxConcurrentRequests = options.Value.MaxConcurrentRequests;
|
_maxConcurrentRequests = options.Value.MaxConcurrentRequests;
|
||||||
if (_maxConcurrentRequests <= 0)
|
if (_maxConcurrentRequests <= 0)
|
||||||
|
|
@ -4,7 +4,6 @@
|
||||||
using System;
|
using System;
|
||||||
using System.Threading;
|
using System.Threading;
|
||||||
using System.Threading.Tasks;
|
using System.Threading.Tasks;
|
||||||
using System.Xml.Schema;
|
|
||||||
using Microsoft.AspNetCore.Http;
|
using Microsoft.AspNetCore.Http;
|
||||||
using Microsoft.Extensions.Logging;
|
using Microsoft.Extensions.Logging;
|
||||||
using Microsoft.Extensions.Options;
|
using Microsoft.Extensions.Options;
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,127 @@
|
||||||
|
using System;
|
||||||
|
using System.Threading.Tasks;
|
||||||
|
using Microsoft.AspNetCore.RequestThrottling.QueuePolicies;
|
||||||
|
using Microsoft.Extensions.Options;
|
||||||
|
using Xunit;
|
||||||
|
|
||||||
|
namespace Microsoft.AspNetCore.RequestThrottling.Tests.PolicyTests
|
||||||
|
{
|
||||||
|
public static class StackQueueTests
|
||||||
|
{
|
||||||
|
[Fact]
|
||||||
|
public static void BaseFunctionality()
|
||||||
|
{
|
||||||
|
var stack = new StackQueuePolicy(Options.Create(new QueuePolicyOptions {
|
||||||
|
MaxConcurrentRequests = 0,
|
||||||
|
RequestQueueLimit = 2,
|
||||||
|
}));
|
||||||
|
|
||||||
|
var task1 = stack.TryEnterAsync();
|
||||||
|
|
||||||
|
Assert.False(task1.IsCompleted);
|
||||||
|
|
||||||
|
stack.OnExit();
|
||||||
|
|
||||||
|
Assert.True(task1.IsCompleted && task1.Result);
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public static void OldestRequestOverwritten()
|
||||||
|
{
|
||||||
|
var stack = new StackQueuePolicy(Options.Create(new QueuePolicyOptions {
|
||||||
|
MaxConcurrentRequests = 0,
|
||||||
|
RequestQueueLimit = 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.False(task4.IsCompleted);
|
||||||
|
|
||||||
|
Assert.True(task1.IsCompleted);
|
||||||
|
Assert.False(task1.Result);
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public static void RespectsMaxConcurrency()
|
||||||
|
{
|
||||||
|
var stack = new StackQueuePolicy(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 static void ExitRequestsPreserveSemaphoreState()
|
||||||
|
{
|
||||||
|
var stack = new StackQueuePolicy(Options.Create(new QueuePolicyOptions {
|
||||||
|
MaxConcurrentRequests = 1,
|
||||||
|
RequestQueueLimit = 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 = new StackQueuePolicy(Options.Create(new QueuePolicyOptions
|
||||||
|
{
|
||||||
|
MaxConcurrentRequests = 0,
|
||||||
|
RequestQueueLimit = 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 = new StackQueuePolicy(Options.Create(new QueuePolicyOptions
|
||||||
|
{
|
||||||
|
MaxConcurrentRequests = 1,
|
||||||
|
RequestQueueLimit = 4,
|
||||||
|
}));
|
||||||
|
|
||||||
|
Assert.Throws<InvalidOperationException>(() => stack.OnExit());
|
||||||
|
|
||||||
|
await stack.TryEnterAsync();
|
||||||
|
|
||||||
|
stack.OnExit();
|
||||||
|
|
||||||
|
Assert.Throws<InvalidOperationException>(() => stack.OnExit());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -5,7 +5,7 @@ using System;
|
||||||
using System.Threading;
|
using System.Threading;
|
||||||
using System.Threading.Tasks;
|
using System.Threading.Tasks;
|
||||||
using Microsoft.AspNetCore.Http;
|
using Microsoft.AspNetCore.Http;
|
||||||
using Microsoft.AspNetCore.RequestThrottling.Policies;
|
using Microsoft.AspNetCore.RequestThrottling.QueuePolicies;
|
||||||
using Microsoft.Extensions.Logging.Abstractions;
|
using Microsoft.Extensions.Logging.Abstractions;
|
||||||
using Microsoft.Extensions.Options;
|
using Microsoft.Extensions.Options;
|
||||||
|
|
||||||
|
|
@ -37,15 +37,35 @@ namespace Microsoft.AspNetCore.RequestThrottling.Tests
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
internal static TailDrop CreateTailDropQueue(int maxConcurrentRequests, int requestQueueLimit = 5000)
|
public static RequestThrottlingMiddleware CreateTestMiddleware_StackPolicy(int maxConcurrentRequests, int requestQueueLimit, RequestDelegate onRejected = null, RequestDelegate next = null)
|
||||||
{
|
{
|
||||||
var options = Options.Create(new TailDropOptions
|
return CreateTestMiddleware(
|
||||||
|
queue: CreateStackPolicy(maxConcurrentRequests, requestQueueLimit),
|
||||||
|
onRejected: onRejected,
|
||||||
|
next: next
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
internal static StackQueuePolicy CreateStackPolicy(int maxConcurrentRequests, int requestsQueuelimit = 100)
|
||||||
|
{
|
||||||
|
var options = Options.Create(new QueuePolicyOptions
|
||||||
|
{
|
||||||
|
MaxConcurrentRequests = maxConcurrentRequests,
|
||||||
|
RequestQueueLimit = requestsQueuelimit
|
||||||
|
});
|
||||||
|
|
||||||
|
return new StackQueuePolicy(options);
|
||||||
|
}
|
||||||
|
|
||||||
|
internal static TailDropQueuePolicy CreateTailDropQueue(int maxConcurrentRequests, int requestQueueLimit = 100)
|
||||||
|
{
|
||||||
|
var options = Options.Create(new QueuePolicyOptions
|
||||||
{
|
{
|
||||||
MaxConcurrentRequests = maxConcurrentRequests,
|
MaxConcurrentRequests = maxConcurrentRequests,
|
||||||
RequestQueueLimit = requestQueueLimit
|
RequestQueueLimit = requestQueueLimit
|
||||||
});
|
});
|
||||||
|
|
||||||
return new TailDrop(options);
|
return new TailDropQueuePolicy(options);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue