Dylan/request throttle (#10413)
* request throttling -- initial implementation * prevented semaphore leak; added xml docs * small doc fixes * reference document * Added internals folder, added structured logging, * removed typo'd dependency * no default MaxConcurrentRequests; other polishing * renamed SemaphoreWrapper->RequestQueue; cleanup * moved SyncPoint; prevented possible semaphore leak * adjusting feedback * regen refs * Final changes!
This commit is contained in:
parent
01d20c134c
commit
9969e99ef4
|
|
@ -5,6 +5,8 @@
|
|||
</PropertyGroup>
|
||||
<ItemGroup Condition="'$(TargetFramework)' == 'netcoreapp3.0'">
|
||||
<Compile Include="Microsoft.AspNetCore.RequestThrottling.netcoreapp3.0.cs" />
|
||||
|
||||
<Reference Include="Microsoft.AspNetCore.Http.Abstractions" />
|
||||
<Reference Include="Microsoft.Extensions.Logging.Abstractions" />
|
||||
<Reference Include="Microsoft.Extensions.Options" />
|
||||
</ItemGroup>
|
||||
</Project>
|
||||
|
|
|
|||
|
|
@ -1,3 +1,24 @@
|
|||
// 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.Builder
|
||||
{
|
||||
public static partial class RequestThrottlingExtensions
|
||||
{
|
||||
public static Microsoft.AspNetCore.Builder.IApplicationBuilder UseRequestThrottling(this Microsoft.AspNetCore.Builder.IApplicationBuilder app) { throw null; }
|
||||
}
|
||||
}
|
||||
namespace Microsoft.AspNetCore.RequestThrottling
|
||||
{
|
||||
public partial class RequestThrottlingMiddleware
|
||||
{
|
||||
public RequestThrottlingMiddleware(Microsoft.AspNetCore.Http.RequestDelegate next, Microsoft.Extensions.Logging.ILoggerFactory loggerFactory, Microsoft.Extensions.Options.IOptions<Microsoft.AspNetCore.RequestThrottling.RequestThrottlingOptions> options) { }
|
||||
[System.Diagnostics.DebuggerStepThroughAttribute]
|
||||
public System.Threading.Tasks.Task Invoke(Microsoft.AspNetCore.Http.HttpContext context) { throw null; }
|
||||
}
|
||||
public partial class RequestThrottlingOptions
|
||||
{
|
||||
public RequestThrottlingOptions() { }
|
||||
public int? MaxConcurrentRequests { [System.Runtime.CompilerServices.CompilerGeneratedAttribute]get { throw null; } [System.Runtime.CompilerServices.CompilerGeneratedAttribute]set { } }
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,13 +1,13 @@
|
|||
<Project Sdk="Microsoft.NET.Sdk.Web">
|
||||
<Project Sdk="Microsoft.NET.Sdk.Web">
|
||||
|
||||
<PropertyGroup>
|
||||
<TargetFramework>netcoreapp3.0</TargetFramework>
|
||||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<Reference Include="Microsoft.AspNetCore.Server.Kestrel" />
|
||||
<Reference Include="Microsoft.Extensions.Logging.Console" />
|
||||
<Reference Include="Microsoft.AspNetCore.RequestThrottling" />
|
||||
<Reference Include="Microsoft.AspNetCore.Server.Kestrel" />
|
||||
</ItemGroup>
|
||||
|
||||
</Project>
|
||||
|
|
|
|||
|
|
@ -1,12 +1,12 @@
|
|||
using System;
|
||||
using System.Collections.Generic;
|
||||
// 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.IO;
|
||||
using System.Linq;
|
||||
using System.Net;
|
||||
using System.Threading.Tasks;
|
||||
using Microsoft.AspNetCore.Builder;
|
||||
using Microsoft.AspNetCore.Hosting;
|
||||
using Microsoft.AspNetCore.Http;
|
||||
using Microsoft.AspNetCore.RequestThrottling;
|
||||
using Microsoft.Extensions.DependencyInjection;
|
||||
using Microsoft.Extensions.Hosting;
|
||||
using Microsoft.Extensions.Logging;
|
||||
|
|
@ -19,13 +19,20 @@ namespace RequestThrottlingSample
|
|||
// For more information on how to configure your application, visit https://go.microsoft.com/fwlink/?LinkID=398940
|
||||
public void ConfigureServices(IServiceCollection services)
|
||||
{
|
||||
services.Configure<RequestThrottlingOptions>(options =>
|
||||
{
|
||||
options.MaxConcurrentRequests = 2;
|
||||
});
|
||||
}
|
||||
|
||||
public void Configure(IApplicationBuilder app, IWebHostEnvironment environment)
|
||||
public void Configure(IApplicationBuilder app, ILoggerFactory loggerFactory)
|
||||
{
|
||||
app.UseRequestThrottling();
|
||||
|
||||
app.Run(async context =>
|
||||
{
|
||||
await context.Response.WriteAsync("Hello world!");
|
||||
await context.Response.WriteAsync("Hello Request Throttling! <p></p>");
|
||||
await Task.Delay(1000);
|
||||
});
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -0,0 +1,64 @@
|
|||
// 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.Threading;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace Microsoft.AspNetCore.RequestThrottling.Internal
|
||||
{
|
||||
internal class RequestQueue : IDisposable
|
||||
{
|
||||
private SemaphoreSlim _semaphore;
|
||||
private object _waitingRequestsLock = new object();
|
||||
public readonly int MaxConcurrentRequests;
|
||||
public int WaitingRequests { get; private set; }
|
||||
|
||||
public RequestQueue(int maxConcurrentRequests)
|
||||
{
|
||||
MaxConcurrentRequests = maxConcurrentRequests;
|
||||
_semaphore = new SemaphoreSlim(maxConcurrentRequests);
|
||||
}
|
||||
|
||||
public async Task EnterQueue()
|
||||
{
|
||||
var waitInQueueTask = _semaphore.WaitAsync();
|
||||
|
||||
var needsToWaitOnQueue = !waitInQueueTask.IsCompletedSuccessfully;
|
||||
if (needsToWaitOnQueue)
|
||||
{
|
||||
lock (_waitingRequestsLock)
|
||||
{
|
||||
WaitingRequests++;
|
||||
}
|
||||
|
||||
await waitInQueueTask;
|
||||
|
||||
lock (_waitingRequestsLock)
|
||||
{
|
||||
WaitingRequests--;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public void Release()
|
||||
{
|
||||
_semaphore.Release();
|
||||
}
|
||||
|
||||
public int Count
|
||||
{
|
||||
get => _semaphore.CurrentCount;
|
||||
}
|
||||
|
||||
public int ConcurrentRequests
|
||||
{
|
||||
get => MaxConcurrentRequests - _semaphore.CurrentCount;
|
||||
}
|
||||
|
||||
public void Dispose()
|
||||
{
|
||||
_semaphore.Dispose();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -1,4 +1,4 @@
|
|||
<Project Sdk="Microsoft.NET.Sdk">
|
||||
<Project Sdk="Microsoft.NET.Sdk">
|
||||
|
||||
<PropertyGroup>
|
||||
<Description>ASP.NET Core middleware for queuing incoming HTTP requests, to avoid threadpool starvation.</Description>
|
||||
|
|
@ -7,4 +7,10 @@
|
|||
<PackageTags>aspnetcore;queue;queuing</PackageTags>
|
||||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<Reference Include="Microsoft.AspNetCore.Http.Abstractions" />
|
||||
<Reference Include="Microsoft.Extensions.Logging.Abstractions" />
|
||||
<Reference Include="Microsoft.Extensions.Options" />
|
||||
</ItemGroup>
|
||||
|
||||
</Project>
|
||||
|
|
|
|||
|
|
@ -0,0 +1,29 @@
|
|||
// 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;
|
||||
|
||||
namespace Microsoft.AspNetCore.Builder
|
||||
{
|
||||
/// <summary>
|
||||
/// Extension methods for adding the <see cref="RequestThrottlingMiddleware"/> to an application.
|
||||
/// </summary>
|
||||
public static class RequestThrottlingExtensions
|
||||
{
|
||||
/// <summary>
|
||||
/// Adds the <see cref="RequestThrottlingMiddleware"/> to limit the number of concurrently-executing requests.
|
||||
/// </summary>
|
||||
/// <param name="app">The <see cref="IApplicationBuilder"/>.</param>
|
||||
/// <returns>The <see cref="IApplicationBuilder"/>.</returns>
|
||||
public static IApplicationBuilder UseRequestThrottling(this IApplicationBuilder app)
|
||||
{
|
||||
if (app == null)
|
||||
{
|
||||
throw new ArgumentNullException(nameof(app));
|
||||
}
|
||||
|
||||
return app.UseMiddleware<RequestThrottlingMiddleware>();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,116 @@
|
|||
// 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.Threading.Tasks;
|
||||
using Microsoft.AspNetCore.Http;
|
||||
using Microsoft.AspNetCore.RequestThrottling.Internal;
|
||||
using Microsoft.Extensions.Logging;
|
||||
using Microsoft.Extensions.Options;
|
||||
|
||||
namespace Microsoft.AspNetCore.RequestThrottling
|
||||
{
|
||||
/// <summary>
|
||||
/// Limits the number of concurrent requests allowed in the application.
|
||||
/// </summary>
|
||||
public class RequestThrottlingMiddleware
|
||||
{
|
||||
private readonly RequestQueue _requestQueue;
|
||||
private readonly RequestThrottlingOptions _options;
|
||||
private readonly RequestDelegate _next;
|
||||
private readonly ILogger _logger;
|
||||
|
||||
/// <summary>
|
||||
/// Creates a new <see cref="RequestThrottlingMiddleware"/>.
|
||||
/// </summary>
|
||||
/// <param name="next">The <see cref="RequestDelegate"/> representing the next middleware in the pipeline.</param>
|
||||
/// <param name="loggerFactory">The <see cref="ILoggerFactory"/> used for logging.</param>
|
||||
/// <param name="options">The <see cref="RequestThrottlingOptions"/> containing the initialization parameters.</param>
|
||||
public RequestThrottlingMiddleware(RequestDelegate next, ILoggerFactory loggerFactory, IOptions<RequestThrottlingOptions> options)
|
||||
{
|
||||
if (options.Value.MaxConcurrentRequests == null)
|
||||
{
|
||||
throw new ArgumentException("The value of 'options.MaxConcurrentRequests' must be specified.", nameof(options));
|
||||
}
|
||||
|
||||
_next = next;
|
||||
_logger = loggerFactory.CreateLogger<RequestThrottlingMiddleware>();
|
||||
_options = options.Value;
|
||||
_requestQueue = new RequestQueue(_options.MaxConcurrentRequests.Value);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Invokes the logic of the middleware.
|
||||
/// </summary>
|
||||
/// <param name="context">The <see cref="HttpContext"/>.</param>
|
||||
/// <returns>A <see cref="Task"/> that completes when the request leaves.</returns>
|
||||
public async Task Invoke(HttpContext context)
|
||||
{
|
||||
var waitInQueueTask = _requestQueue.EnterQueue();
|
||||
|
||||
if (waitInQueueTask.IsCompletedSuccessfully)
|
||||
{
|
||||
RequestThrottlingLog.RequestRunImmediately(_logger);
|
||||
}
|
||||
else
|
||||
{
|
||||
RequestThrottlingLog.RequestEnqueued(_logger, WaitingRequests);
|
||||
await waitInQueueTask;
|
||||
RequestThrottlingLog.RequestDequeued(_logger, WaitingRequests);
|
||||
}
|
||||
|
||||
try
|
||||
{
|
||||
await _next(context);
|
||||
}
|
||||
finally
|
||||
{
|
||||
_requestQueue.Release();
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// The number of live requests that are downstream from this middleware.
|
||||
/// Cannot exceeed <see cref="RequestThrottlingOptions.MaxConcurrentRequests"/>.
|
||||
/// </summary>
|
||||
internal int ConcurrentRequests
|
||||
{
|
||||
get => _requestQueue.ConcurrentRequests;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Number of requests currently enqueued and waiting to be processed.
|
||||
/// </summary>
|
||||
internal int WaitingRequests
|
||||
{
|
||||
get => _requestQueue.WaitingRequests;
|
||||
}
|
||||
|
||||
private static class RequestThrottlingLog
|
||||
{
|
||||
private static readonly Action<ILogger, int, Exception> _requestEnqueued =
|
||||
LoggerMessage.Define<int>(LogLevel.Debug, new EventId(1, "RequestEnqueued"), "Concurrent request limit reached, queuing request. Current queue length: {QueuedRequests}.");
|
||||
|
||||
private static readonly Action<ILogger, int, Exception> _requestDequeued =
|
||||
LoggerMessage.Define<int>(LogLevel.Debug, new EventId(2, "RequestDequeued"), "Request dequeued. Current queue length: {QueuedRequests}.");
|
||||
|
||||
private static readonly Action<ILogger, Exception> _requestRunImmediately =
|
||||
LoggerMessage.Define(LogLevel.Debug, new EventId(3, "RequestRunImmediately"), "Concurrent request limit has not been reached, running request immediately.");
|
||||
|
||||
internal static void RequestEnqueued(ILogger logger, int queuedRequests)
|
||||
{
|
||||
_requestEnqueued(logger, queuedRequests, null);
|
||||
}
|
||||
|
||||
internal static void RequestDequeued(ILogger logger, int queuedRequests)
|
||||
{
|
||||
_requestDequeued(logger, queuedRequests, null);
|
||||
}
|
||||
|
||||
internal static void RequestRunImmediately(ILogger logger)
|
||||
{
|
||||
_requestRunImmediately(logger, null);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -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 Microsoft.AspNetCore.RequestThrottling;
|
||||
|
||||
namespace Microsoft.AspNetCore.RequestThrottling
|
||||
{
|
||||
/// <summary>
|
||||
/// Specifies options for the <see cref="RequestThrottlingMiddleware"/>.
|
||||
/// </summary>
|
||||
public class RequestThrottlingOptions
|
||||
{
|
||||
/// <summary>
|
||||
/// Maximum number of concurrent requests. Any extras will be queued on the server.
|
||||
/// This is null by default because the correct value is application specific. This option must be configured by the application.
|
||||
/// </summary>
|
||||
public int? MaxConcurrentRequests { get; set; }
|
||||
}
|
||||
}
|
||||
|
|
@ -1,38 +0,0 @@
|
|||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Text;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace Microsoft.AspNetCore.RequestThrottling
|
||||
{
|
||||
internal class SemaphoreWrapper : IDisposable
|
||||
{
|
||||
private SemaphoreSlim _semaphore;
|
||||
|
||||
public SemaphoreWrapper(int queueLength)
|
||||
{
|
||||
_semaphore = new SemaphoreSlim(queueLength);
|
||||
}
|
||||
|
||||
public Task EnterQueue()
|
||||
{
|
||||
return _semaphore.WaitAsync();
|
||||
}
|
||||
|
||||
public void LeaveQueue()
|
||||
{
|
||||
_semaphore.Release();
|
||||
}
|
||||
|
||||
public int Count
|
||||
{
|
||||
get => _semaphore.CurrentCount;
|
||||
}
|
||||
|
||||
public void Dispose()
|
||||
{
|
||||
_semaphore.Dispose();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -1,10 +1,16 @@
|
|||
<Project Sdk="Microsoft.NET.Sdk">
|
||||
<Project Sdk="Microsoft.NET.Sdk">
|
||||
|
||||
<PropertyGroup>
|
||||
<TargetFramework>netcoreapp3.0</TargetFramework>
|
||||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<Compile Include="$(SharedSourceRoot)SyncPoint\SyncPoint.cs" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<Reference Include="Microsoft.AspNetCore.Http" />
|
||||
<Reference Include="Microsoft.AspNetCore.Hosting" />
|
||||
<Reference Include="Microsoft.AspNetCore.RequestThrottling" />
|
||||
</ItemGroup>
|
||||
</Project>
|
||||
|
|
|
|||
|
|
@ -0,0 +1,86 @@
|
|||
// 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.Threading.Tasks;
|
||||
using Microsoft.AspNetCore.Http;
|
||||
using Microsoft.AspNetCore.Internal;
|
||||
using Xunit;
|
||||
|
||||
namespace Microsoft.AspNetCore.RequestThrottling.Tests
|
||||
{
|
||||
public class MiddlewareTests
|
||||
{
|
||||
[Fact]
|
||||
public async Task RequestsCanEnterIfSpaceAvailible()
|
||||
{
|
||||
var middleware = TestUtils.CreateTestMiddleWare(maxConcurrentRequests: 1);
|
||||
var context = new DefaultHttpContext();
|
||||
|
||||
// a request should go through with no problems
|
||||
await middleware.Invoke(context).OrTimeout();
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task SemaphoreStatePreservedIfRequestsError()
|
||||
{
|
||||
var middleware = TestUtils.CreateTestMiddleWare(
|
||||
maxConcurrentRequests: 1,
|
||||
next: httpContext =>
|
||||
{
|
||||
throw new DivideByZeroException();
|
||||
});
|
||||
|
||||
Assert.Equal(0, middleware.ConcurrentRequests);
|
||||
|
||||
await Assert.ThrowsAsync<DivideByZeroException>(() => middleware.Invoke(new DefaultHttpContext()));
|
||||
|
||||
Assert.Equal(0, middleware.ConcurrentRequests);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task QueuedRequestsContinueWhenSpaceBecomesAvailible()
|
||||
{
|
||||
var blocker = new SyncPoint();
|
||||
var firstRequest = true;
|
||||
|
||||
var middleware = TestUtils.CreateTestMiddleWare(
|
||||
maxConcurrentRequests: 1,
|
||||
next: httpContext =>
|
||||
{
|
||||
if (firstRequest)
|
||||
{
|
||||
firstRequest = false;
|
||||
return blocker.WaitToContinue();
|
||||
}
|
||||
return Task.CompletedTask;
|
||||
});
|
||||
|
||||
// t1 (as the first request) is blocked by the tcs blocker
|
||||
var t1 = middleware.Invoke(new DefaultHttpContext());
|
||||
Assert.Equal(1, middleware.ConcurrentRequests);
|
||||
Assert.Equal(0, middleware.WaitingRequests);
|
||||
|
||||
// t2 is blocked from entering the server since t1 already exists there
|
||||
// note: increasing MaxConcurrentRequests would allow t2 through while t1 is blocked
|
||||
var t2 = middleware.Invoke(new DefaultHttpContext());
|
||||
Assert.Equal(1, middleware.ConcurrentRequests);
|
||||
Assert.Equal(1, middleware.WaitingRequests);
|
||||
|
||||
// unblock the first task, and the second should follow
|
||||
blocker.Continue();
|
||||
await t1.OrTimeout();
|
||||
await t2.OrTimeout();
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void InvalidArgumentIfMaxConcurrentRequestsIsNull()
|
||||
{
|
||||
var ex = Assert.Throws<ArgumentException>(() =>
|
||||
{
|
||||
TestUtils.CreateTestMiddleWare(maxConcurrentRequests: null);
|
||||
});
|
||||
Assert.Equal("options", ex.ParamName);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -1,34 +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 Xunit;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using System;
|
||||
using System.Runtime.CompilerServices;
|
||||
using Microsoft.AspNetCore.Testing;
|
||||
using Microsoft.AspNetCore.RequestThrottling.Internal;
|
||||
using Xunit;
|
||||
|
||||
namespace Microsoft.AspNetCore.RequestThrottling.Tests
|
||||
{
|
||||
public class SemaphoreWrapperTests
|
||||
public class RequestQueueTests
|
||||
{
|
||||
[Fact]
|
||||
public async Task TracksQueueLength()
|
||||
public async Task LimitsIncomingRequests()
|
||||
{
|
||||
using var s = new SemaphoreWrapper(1);
|
||||
using var s = new RequestQueue(1);
|
||||
Assert.Equal(1, s.Count);
|
||||
|
||||
await s.EnterQueue().OrTimeout();
|
||||
Assert.Equal(0, s.Count);
|
||||
|
||||
s.LeaveQueue();
|
||||
s.Release();
|
||||
Assert.Equal(1, s.Count);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task TracksQueueLength()
|
||||
{
|
||||
using var s = new RequestQueue(1);
|
||||
Assert.Equal(0, s.WaitingRequests);
|
||||
|
||||
await s.EnterQueue();
|
||||
Assert.Equal(0, s.WaitingRequests);
|
||||
|
||||
var enterQueueTask = s.EnterQueue();
|
||||
Assert.Equal(1, s.WaitingRequests);
|
||||
|
||||
s.Release();
|
||||
await enterQueueTask;
|
||||
Assert.Equal(0, s.WaitingRequests);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void DoesNotWaitIfSpaceAvailible()
|
||||
{
|
||||
using var s = new SemaphoreWrapper(2);
|
||||
using var s = new RequestQueue(2);
|
||||
|
||||
var t1 = s.EnterQueue();
|
||||
Assert.True(t1.IsCompleted);
|
||||
|
|
@ -43,21 +57,21 @@ namespace Microsoft.AspNetCore.RequestThrottling.Tests
|
|||
[Fact]
|
||||
public async Task WaitsIfNoSpaceAvailible()
|
||||
{
|
||||
using var s = new SemaphoreWrapper(1);
|
||||
using var s = new RequestQueue(1);
|
||||
await s.EnterQueue().OrTimeout();
|
||||
|
||||
var waitingTask = s.EnterQueue();
|
||||
Assert.False(waitingTask.IsCompleted);
|
||||
|
||||
s.LeaveQueue();
|
||||
s.Release();
|
||||
await waitingTask.OrTimeout();
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task IsEncapsulated()
|
||||
{
|
||||
using var s1 = new SemaphoreWrapper(1);
|
||||
using var s2 = new SemaphoreWrapper(1);
|
||||
using var s1 = new RequestQueue(1);
|
||||
using var s2 = new RequestQueue(1);
|
||||
|
||||
await s1.EnterQueue().OrTimeout();
|
||||
await s2.EnterQueue().OrTimeout();
|
||||
|
|
@ -0,0 +1,28 @@
|
|||
// 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.Tasks;
|
||||
using Microsoft.AspNetCore.RequestThrottling;
|
||||
using Microsoft.AspNetCore.Http;
|
||||
using Microsoft.Extensions.Logging.Abstractions;
|
||||
using Microsoft.Extensions.Options;
|
||||
|
||||
namespace Microsoft.AspNetCore.RequestThrottling.Tests
|
||||
{
|
||||
public static class TestUtils
|
||||
{
|
||||
public static RequestThrottlingMiddleware CreateTestMiddleWare(int? maxConcurrentRequests, RequestDelegate next = null)
|
||||
{
|
||||
var options = new RequestThrottlingOptions
|
||||
{
|
||||
MaxConcurrentRequests = maxConcurrentRequests
|
||||
};
|
||||
|
||||
return new RequestThrottlingMiddleware(
|
||||
next: next ?? (context => Task.CompletedTask),
|
||||
loggerFactory: NullLoggerFactory.Instance,
|
||||
options: Options.Create(options)
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -1,4 +1,4 @@
|
|||
// 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.
|
||||
|
||||
using System;
|
||||
|
|
|
|||
|
|
@ -1,4 +1,4 @@
|
|||
<Project Sdk="Microsoft.NET.Sdk">
|
||||
<Project Sdk="Microsoft.NET.Sdk">
|
||||
|
||||
<PropertyGroup>
|
||||
<Description>ASP.NET Core middleware for caching HTTP responses on the server.</Description>
|
||||
|
|
|
|||
|
|
@ -0,0 +1,10 @@
|
|||
<!-- This file is automatically generated. -->
|
||||
<Project Sdk="Microsoft.NET.Sdk">
|
||||
<PropertyGroup>
|
||||
<TargetFrameworks>netcoreapp3.0</TargetFrameworks>
|
||||
</PropertyGroup>
|
||||
<ItemGroup Condition="'$(TargetFramework)' == 'netcoreapp3.0'">
|
||||
<Compile Include="Microsoft.AspNetCore.ANCMSymbols.netcoreapp3.0.cs" />
|
||||
|
||||
</ItemGroup>
|
||||
</Project>
|
||||
|
|
@ -0,0 +1,3 @@
|
|||
// 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.
|
||||
|
||||
|
|
@ -4,7 +4,7 @@
|
|||
using System;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace Microsoft.AspNetCore.SignalR.Tests
|
||||
namespace Microsoft.AspNetCore.Internal
|
||||
{
|
||||
public class SyncPoint
|
||||
{
|
||||
|
|
@ -10,6 +10,7 @@ using Microsoft.AspNetCore.Connections;
|
|||
using Microsoft.AspNetCore.Http.Connections;
|
||||
using Microsoft.AspNetCore.Http.Connections.Client;
|
||||
using Microsoft.AspNetCore.Http.Connections.Client.Internal;
|
||||
using Microsoft.AspNetCore.Internal;
|
||||
using Microsoft.AspNetCore.SignalR.Tests;
|
||||
using Microsoft.Extensions.Logging;
|
||||
using Microsoft.Extensions.Logging.Testing;
|
||||
|
|
@ -90,8 +91,8 @@ namespace Microsoft.AspNetCore.SignalR.Client.Tests
|
|||
var startCounter = 0;
|
||||
var expected = new Exception("Transport failed to start");
|
||||
|
||||
// We have 4 cases here. Falling back once, falling back twice and each of these
|
||||
// with WebSockets available and not. If Websockets aren't available and
|
||||
// We have 4 cases here. Falling back once, falling back twice and each of these
|
||||
// with WebSockets available and not. If Websockets aren't available and
|
||||
// we can't to test the fallback once scenario we don't decrement the passthreshold
|
||||
// because we still try to start twice (SSE and LP).
|
||||
if (!TestHelpers.IsWebSocketsSupported() && passThreshold > 2)
|
||||
|
|
|
|||
|
|
@ -8,6 +8,7 @@ using System.Text;
|
|||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using Microsoft.AspNetCore.Connections;
|
||||
using Microsoft.AspNetCore.Internal;
|
||||
using Microsoft.AspNetCore.SignalR.Protocol;
|
||||
using Microsoft.AspNetCore.SignalR.Tests;
|
||||
using Microsoft.Extensions.DependencyInjection;
|
||||
|
|
|
|||
|
|
@ -1,4 +1,4 @@
|
|||
<Project Sdk="Microsoft.NET.Sdk">
|
||||
<Project Sdk="Microsoft.NET.Sdk">
|
||||
|
||||
<PropertyGroup>
|
||||
<TargetFramework>netcoreapp3.0</TargetFramework>
|
||||
|
|
@ -8,6 +8,7 @@
|
|||
<Compile Include="$(SignalRSharedSourceRoot)MemoryBufferWriter.cs" Link="MemoryBufferWriter.cs" />
|
||||
<Compile Include="$(SignalRSharedSourceRoot)TextMessageFormatter.cs" Link="TextMessageFormatter.cs" />
|
||||
<Compile Include="$(SignalRSharedSourceRoot)TextMessageParser.cs" Link="TextMessageParser.cs" />
|
||||
<Compile Include="$(SharedSourceRoot)SyncPoint\SyncPoint.cs" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
|
|
|
|||
|
|
@ -11,6 +11,7 @@ using System.Threading;
|
|||
using System.Threading.Tasks;
|
||||
using Microsoft.AspNetCore.Connections;
|
||||
using Microsoft.AspNetCore.Http.Connections.Client.Internal;
|
||||
using Microsoft.AspNetCore.Internal;
|
||||
using Microsoft.AspNetCore.SignalR.Tests;
|
||||
using Microsoft.Extensions.Logging.Testing;
|
||||
using Moq;
|
||||
|
|
|
|||
|
|
@ -21,6 +21,7 @@ using Microsoft.AspNetCore.Http.Connections.Internal;
|
|||
using Microsoft.AspNetCore.Http.Connections.Internal.Transports;
|
||||
using Microsoft.AspNetCore.Http.Features;
|
||||
using Microsoft.AspNetCore.Http.Internal;
|
||||
using Microsoft.AspNetCore.Internal;
|
||||
using Microsoft.AspNetCore.SignalR.Tests;
|
||||
using Microsoft.AspNetCore.Testing;
|
||||
using Microsoft.AspNetCore.Testing.xunit;
|
||||
|
|
|
|||
|
|
@ -1,4 +1,4 @@
|
|||
<Project Sdk="Microsoft.NET.Sdk">
|
||||
<Project Sdk="Microsoft.NET.Sdk">
|
||||
|
||||
<PropertyGroup>
|
||||
<TargetFramework>netcoreapp3.0</TargetFramework>
|
||||
|
|
@ -6,6 +6,7 @@
|
|||
|
||||
<ItemGroup>
|
||||
<Compile Include="$(SharedSourceRoot)Buffers.Testing\**\*.cs" />
|
||||
<Compile Include="$(SharedSourceRoot)SyncPoint\SyncPoint.cs" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
|
|
|
|||
|
|
@ -1,9 +1,12 @@
|
|||
<Project Sdk="Microsoft.NET.Sdk">
|
||||
<Project Sdk="Microsoft.NET.Sdk">
|
||||
|
||||
<PropertyGroup>
|
||||
<TargetFramework>netcoreapp3.0</TargetFramework>
|
||||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<Compile Include="$(SharedSourceRoot)SyncPoint\SyncPoint.cs" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<ProjectReference Include="$(SignalRTestUtilsProject)" />
|
||||
|
|
@ -23,4 +26,4 @@
|
|||
<Reference Include="System.Reactive.Linq" />
|
||||
</ItemGroup>
|
||||
|
||||
</Project>
|
||||
</Project>
|
||||
|
|
@ -1,5 +1,6 @@
|
|||
using System.Linq;
|
||||
using System.Threading.Tasks;
|
||||
using Microsoft.AspNetCore.Internal;
|
||||
using Microsoft.AspNetCore.SignalR.Protocol;
|
||||
using Xunit;
|
||||
|
||||
|
|
|
|||
Loading…
Reference in New Issue