Merge pull request #10275 from aspnet/dylan/requestQ

start of request queue
This commit is contained in:
Dylan Dmitri Gray 2019-05-20 17:02:47 -07:00 committed by GitHub
commit 55555bedd5
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
14 changed files with 350 additions and 0 deletions

View File

@ -83,6 +83,7 @@
<ProjectReferenceProvider Include="Microsoft.AspNetCore.Localization" ProjectPath="$(RepoRoot)src\Middleware\Localization\src\Microsoft.AspNetCore.Localization.csproj" RefProjectPath="$(RepoRoot)src\Middleware\Localization\ref\Microsoft.AspNetCore.Localization.csproj" />
<ProjectReferenceProvider Include="Microsoft.AspNetCore.MiddlewareAnalysis" ProjectPath="$(RepoRoot)src\Middleware\MiddlewareAnalysis\src\Microsoft.AspNetCore.MiddlewareAnalysis.csproj" RefProjectPath="$(RepoRoot)src\Middleware\MiddlewareAnalysis\ref\Microsoft.AspNetCore.MiddlewareAnalysis.csproj" />
<ProjectReferenceProvider Include="Microsoft.AspNetCore.NodeServices" ProjectPath="$(RepoRoot)src\Middleware\NodeServices\src\Microsoft.AspNetCore.NodeServices.csproj" RefProjectPath="$(RepoRoot)src\Middleware\NodeServices\ref\Microsoft.AspNetCore.NodeServices.csproj" />
<ProjectReferenceProvider Include="Microsoft.AspNetCore.RequestThrottling" ProjectPath="$(RepoRoot)src\Middleware\RequestThrottling\src\Microsoft.AspNetCore.RequestThrottling.csproj" RefProjectPath="$(RepoRoot)src\Middleware\RequestThrottling\ref\Microsoft.AspNetCore.RequestThrottling.csproj" />
<ProjectReferenceProvider Include="Microsoft.AspNetCore.ResponseCaching.Abstractions" ProjectPath="$(RepoRoot)src\Middleware\ResponseCaching.Abstractions\src\Microsoft.AspNetCore.ResponseCaching.Abstractions.csproj" RefProjectPath="$(RepoRoot)src\Middleware\ResponseCaching.Abstractions\ref\Microsoft.AspNetCore.ResponseCaching.Abstractions.csproj" />
<ProjectReferenceProvider Include="Microsoft.AspNetCore.ResponseCaching" ProjectPath="$(RepoRoot)src\Middleware\ResponseCaching\src\Microsoft.AspNetCore.ResponseCaching.csproj" RefProjectPath="$(RepoRoot)src\Middleware\ResponseCaching\ref\Microsoft.AspNetCore.ResponseCaching.csproj" />
<ProjectReferenceProvider Include="Microsoft.AspNetCore.ResponseCompression" ProjectPath="$(RepoRoot)src\Middleware\ResponseCompression\src\Microsoft.AspNetCore.ResponseCompression.csproj" RefProjectPath="$(RepoRoot)src\Middleware\ResponseCompression\ref\Microsoft.AspNetCore.ResponseCompression.csproj" />

View File

@ -283,6 +283,14 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "HeaderPropagationSample", "
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Microsoft.AspNetCore.Server.IIS", "..\Servers\IIS\IIS\src\Microsoft.AspNetCore.Server.IIS.csproj", "{B9BE1823-B555-4AAB-AEBC-C8C3F48C8861}"
EndProject
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "RequestThrottling", "RequestThrottling", "{8C9AA8A2-9D1F-4450-9F8D-56BAB6F3D343}"
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "RequestThrottlingSample", "RequestThrottling\sample\RequestThrottlingSample.csproj", "{6720919C-0DEA-49E1-90DC-F1883F7919CD}"
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Microsoft.AspNetCore.RequestThrottling", "RequestThrottling\src\Microsoft.AspNetCore.RequestThrottling.csproj", "{4CE2384D-6B88-4824-ADD1-4183D180FEFF}"
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Microsoft.AspNetCore.RequestThrottling.Tests", "RequestThrottling\test\Microsoft.AspNetCore.RequestThrottling.Tests.csproj", "{353AA2B0-1013-486C-B5BD-9379385CA403}"
EndProject
Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution
Debug|Any CPU = Debug|Any CPU
@ -1541,6 +1549,42 @@ Global
{B9BE1823-B555-4AAB-AEBC-C8C3F48C8861}.Release|x64.Build.0 = Release|Any CPU
{B9BE1823-B555-4AAB-AEBC-C8C3F48C8861}.Release|x86.ActiveCfg = Release|Any CPU
{B9BE1823-B555-4AAB-AEBC-C8C3F48C8861}.Release|x86.Build.0 = Release|Any CPU
{6720919C-0DEA-49E1-90DC-F1883F7919CD}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{6720919C-0DEA-49E1-90DC-F1883F7919CD}.Debug|Any CPU.Build.0 = Debug|Any CPU
{6720919C-0DEA-49E1-90DC-F1883F7919CD}.Debug|x64.ActiveCfg = Debug|Any CPU
{6720919C-0DEA-49E1-90DC-F1883F7919CD}.Debug|x64.Build.0 = Debug|Any CPU
{6720919C-0DEA-49E1-90DC-F1883F7919CD}.Debug|x86.ActiveCfg = Debug|Any CPU
{6720919C-0DEA-49E1-90DC-F1883F7919CD}.Debug|x86.Build.0 = Debug|Any CPU
{6720919C-0DEA-49E1-90DC-F1883F7919CD}.Release|Any CPU.ActiveCfg = Release|Any CPU
{6720919C-0DEA-49E1-90DC-F1883F7919CD}.Release|Any CPU.Build.0 = Release|Any CPU
{6720919C-0DEA-49E1-90DC-F1883F7919CD}.Release|x64.ActiveCfg = Release|Any CPU
{6720919C-0DEA-49E1-90DC-F1883F7919CD}.Release|x64.Build.0 = Release|Any CPU
{6720919C-0DEA-49E1-90DC-F1883F7919CD}.Release|x86.ActiveCfg = Release|Any CPU
{6720919C-0DEA-49E1-90DC-F1883F7919CD}.Release|x86.Build.0 = Release|Any CPU
{4CE2384D-6B88-4824-ADD1-4183D180FEFF}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{4CE2384D-6B88-4824-ADD1-4183D180FEFF}.Debug|Any CPU.Build.0 = Debug|Any CPU
{4CE2384D-6B88-4824-ADD1-4183D180FEFF}.Debug|x64.ActiveCfg = Debug|Any CPU
{4CE2384D-6B88-4824-ADD1-4183D180FEFF}.Debug|x64.Build.0 = Debug|Any CPU
{4CE2384D-6B88-4824-ADD1-4183D180FEFF}.Debug|x86.ActiveCfg = Debug|Any CPU
{4CE2384D-6B88-4824-ADD1-4183D180FEFF}.Debug|x86.Build.0 = Debug|Any CPU
{4CE2384D-6B88-4824-ADD1-4183D180FEFF}.Release|Any CPU.ActiveCfg = Release|Any CPU
{4CE2384D-6B88-4824-ADD1-4183D180FEFF}.Release|Any CPU.Build.0 = Release|Any CPU
{4CE2384D-6B88-4824-ADD1-4183D180FEFF}.Release|x64.ActiveCfg = Release|Any CPU
{4CE2384D-6B88-4824-ADD1-4183D180FEFF}.Release|x64.Build.0 = Release|Any CPU
{4CE2384D-6B88-4824-ADD1-4183D180FEFF}.Release|x86.ActiveCfg = Release|Any CPU
{4CE2384D-6B88-4824-ADD1-4183D180FEFF}.Release|x86.Build.0 = Release|Any CPU
{353AA2B0-1013-486C-B5BD-9379385CA403}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{353AA2B0-1013-486C-B5BD-9379385CA403}.Debug|Any CPU.Build.0 = Debug|Any CPU
{353AA2B0-1013-486C-B5BD-9379385CA403}.Debug|x64.ActiveCfg = Debug|Any CPU
{353AA2B0-1013-486C-B5BD-9379385CA403}.Debug|x64.Build.0 = Debug|Any CPU
{353AA2B0-1013-486C-B5BD-9379385CA403}.Debug|x86.ActiveCfg = Debug|Any CPU
{353AA2B0-1013-486C-B5BD-9379385CA403}.Debug|x86.Build.0 = Debug|Any CPU
{353AA2B0-1013-486C-B5BD-9379385CA403}.Release|Any CPU.ActiveCfg = Release|Any CPU
{353AA2B0-1013-486C-B5BD-9379385CA403}.Release|Any CPU.Build.0 = Release|Any CPU
{353AA2B0-1013-486C-B5BD-9379385CA403}.Release|x64.ActiveCfg = Release|Any CPU
{353AA2B0-1013-486C-B5BD-9379385CA403}.Release|x64.Build.0 = Release|Any CPU
{353AA2B0-1013-486C-B5BD-9379385CA403}.Release|x86.ActiveCfg = Release|Any CPU
{353AA2B0-1013-486C-B5BD-9379385CA403}.Release|x86.Build.0 = Release|Any CPU
EndGlobalSection
GlobalSection(SolutionProperties) = preSolution
HideSolutionNode = FALSE
@ -1663,6 +1707,9 @@ Global
{179A159B-87EA-4353-BE92-4FB6CC05BC7D} = {0437D207-864E-429C-92B4-9D08D290188C}
{CDE2E736-A034-4748-98C4-0DEDAAC8063D} = {179A159B-87EA-4353-BE92-4FB6CC05BC7D}
{B9BE1823-B555-4AAB-AEBC-C8C3F48C8861} = {ACA6DDB9-7592-47CE-A740-D15BF307E9E0}
{6720919C-0DEA-49E1-90DC-F1883F7919CD} = {8C9AA8A2-9D1F-4450-9F8D-56BAB6F3D343}
{4CE2384D-6B88-4824-ADD1-4183D180FEFF} = {8C9AA8A2-9D1F-4450-9F8D-56BAB6F3D343}
{353AA2B0-1013-486C-B5BD-9379385CA403} = {8C9AA8A2-9D1F-4450-9F8D-56BAB6F3D343}
EndGlobalSection
GlobalSection(ExtensibilityGlobals) = postSolution
SolutionGuid = {83786312-A93B-4BB4-AB06-7C6913A59AFA}

View File

@ -0,0 +1,25 @@
{
"solution": {
"path": "..\\Middleware.sln",
"projects": [
"..\\Hosting\\Abstractions\\src\\Microsoft.AspNetCore.Hosting.Abstractions.csproj",
"..\\Hosting\\Hosting\\src\\Microsoft.AspNetCore.Hosting.csproj",
"..\\Hosting\\Server.Abstractions\\src\\Microsoft.AspNetCore.Hosting.Server.Abstractions.csproj",
"..\\Http\\Http.Abstractions\\src\\Microsoft.AspNetCore.Http.Abstractions.csproj",
"..\\Http\\Http.Extensions\\src\\Microsoft.AspNetCore.Http.Extensions.csproj",
"..\\Http\\Http.Features\\src\\Microsoft.AspNetCore.Http.Features.csproj",
"..\\Http\\WebUtilities\\src\\Microsoft.AspNetCore.WebUtilities.csproj",
"..\\Servers\\Connections.Abstractions\\src\\Microsoft.AspNetCore.Connections.Abstractions.csproj",
"..\\Servers\\Kestrel\\Core\\src\\Microsoft.AspNetCore.Server.Kestrel.Core.csproj",
"..\\Servers\\Kestrel\\Kestrel\\src\\Microsoft.AspNetCore.Server.Kestrel.csproj",
"..\\Servers\\Kestrel\\Transport.Abstractions\\src\\Microsoft.AspNetCore.Server.Kestrel.Transport.Abstractions.csproj",
"..\\Servers\\Kestrel\\Transport.Sockets\\src\\Microsoft.AspNetCore.Server.Kestrel.Transport.Sockets.csproj",
"..\\http\\Headers\\src\\Microsoft.Net.Http.Headers.csproj",
"..\\http\\http\\src\\Microsoft.AspNetCore.Http.csproj",
"HttpsPolicy\\src\\Microsoft.AspNetCore.HttpsPolicy.csproj",
"RequestThrottling\\sample\\RequestThrottlingSample.csproj",
"RequestThrottling\\src\\Microsoft.AspNetCore.RequestThrottling.csproj",
"RequestThrottling\\test\\Microsoft.AspNetCore.RequestThrottling.Tests.csproj"
]
}
}

View File

@ -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.RequestThrottling.netcoreapp3.0.cs" />
</ItemGroup>
</Project>

View File

@ -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.

View File

@ -0,0 +1,13 @@
<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" />
</ItemGroup>
</Project>

View File

@ -0,0 +1,49 @@
using System;
using System.Collections.Generic;
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.Extensions.DependencyInjection;
using Microsoft.Extensions.Hosting;
using Microsoft.Extensions.Logging;
namespace RequestThrottlingSample
{
public class Startup
{
// 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
public void ConfigureServices(IServiceCollection services)
{
}
public void Configure(IApplicationBuilder app, IWebHostEnvironment environment)
{
app.Run(async context =>
{
await context.Response.WriteAsync("Hello world!");
});
}
// Entry point for the application.
public static void Main(string[] args)
{
var host = new WebHostBuilder()
.UseKestrel()
.UseContentRoot(Directory.GetCurrentDirectory()) // for the cert file
.ConfigureLogging(factory =>
{
factory.SetMinimumLevel(LogLevel.Debug);
factory.AddConsole();
})
.UseStartup<Startup>()
.Build();
host.Run();
}
}
}

View File

@ -0,0 +1,10 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<Description>ASP.NET Core middleware for queuing incoming HTTP requests, to avoid threadpool starvation.</Description>
<TargetFramework>netcoreapp3.0</TargetFramework>
<GenerateDocumentationFile>true</GenerateDocumentationFile>
<PackageTags>aspnetcore;queue;queuing</PackageTags>
</PropertyGroup>
</Project>

View File

@ -0,0 +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.Runtime.CompilerServices;
[assembly: InternalsVisibleTo("Microsoft.AspNetCore.RequestThrottling.Tests, PublicKey=0024000004800000940000000602000000240000525341310004000001000100f33a29044fa9d740c9b3213a93e57c84b472c84e0b8a0e1ae48e67a9f8f6de9d5f7f3d52ac23e48ac51801f1dc950abe901da34d2a9e3baadb141a17c77ef3c565dd5ee5054b91cf63bb3c6ab83f72ab3aafe93d0fc3c2348b764fafb0b1c0733de51459aeab46580384bf9d74c4e28164b7cde247f891ba07891c9d872ad2bb")]

View File

@ -0,0 +1,38 @@
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();
}
}
}

View File

@ -0,0 +1,10 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFramework>netcoreapp3.0</TargetFramework>
</PropertyGroup>
<ItemGroup>
<Reference Include="Microsoft.AspNetCore.RequestThrottling" />
</ItemGroup>
</Project>

View File

@ -0,0 +1,66 @@
// 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;
namespace Microsoft.AspNetCore.RequestThrottling.Tests
{
public class SemaphoreWrapperTests
{
[Fact]
public async Task TracksQueueLength()
{
using var s = new SemaphoreWrapper(1);
Assert.Equal(1, s.Count);
await s.EnterQueue().OrTimeout();
Assert.Equal(0, s.Count);
s.LeaveQueue();
Assert.Equal(1, s.Count);
}
[Fact]
public void DoesNotWaitIfSpaceAvailible()
{
using var s = new SemaphoreWrapper(2);
var t1 = s.EnterQueue();
Assert.True(t1.IsCompleted);
var t2 = s.EnterQueue();
Assert.True(t2.IsCompleted);
var t3 = s.EnterQueue();
Assert.False(t3.IsCompleted);
}
[Fact]
public async Task WaitsIfNoSpaceAvailible()
{
using var s = new SemaphoreWrapper(1);
await s.EnterQueue().OrTimeout();
var waitingTask = s.EnterQueue();
Assert.False(waitingTask.IsCompleted);
s.LeaveQueue();
await waitingTask.OrTimeout();
}
[Fact]
public async Task IsEncapsulated()
{
using var s1 = new SemaphoreWrapper(1);
using var s2 = new SemaphoreWrapper(1);
await s1.EnterQueue().OrTimeout();
await s2.EnterQueue().OrTimeout();
}
}
}

View File

@ -0,0 +1,69 @@
// 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.Diagnostics;
using System.Runtime.CompilerServices;
using Microsoft.AspNetCore.Testing;
namespace System.Threading.Tasks
{
#if TESTUTILS
public
#else
internal
#endif
static class TaskExtensions
{
private const int DefaultTimeout = 30 * 1000;
public static Task OrTimeout(this Task task, int milliseconds = DefaultTimeout, [CallerMemberName] string memberName = null, [CallerFilePath] string filePath = null, [CallerLineNumber] int? lineNumber = null)
{
return OrTimeout(task, new TimeSpan(0, 0, 0, 0, milliseconds), memberName, filePath, lineNumber);
}
public static Task OrTimeout(this Task task, TimeSpan timeout, [CallerMemberName] string memberName = null, [CallerFilePath] string filePath = null, [CallerLineNumber] int? lineNumber = null)
{
return task.TimeoutAfter(timeout, filePath, lineNumber ?? 0);
}
public static Task<T> OrTimeout<T>(this ValueTask<T> task, int milliseconds = DefaultTimeout, [CallerMemberName] string memberName = null, [CallerFilePath] string filePath = null, [CallerLineNumber] int? lineNumber = null) =>
OrTimeout(task, new TimeSpan(0, 0, 0, 0, milliseconds), memberName, filePath, lineNumber);
public static Task<T> OrTimeout<T>(this ValueTask<T> task, TimeSpan timeout, [CallerMemberName] string memberName = null, [CallerFilePath] string filePath = null, [CallerLineNumber] int? lineNumber = null) =>
task.AsTask().OrTimeout(timeout, memberName, filePath, lineNumber);
public static Task<T> OrTimeout<T>(this Task<T> task, int milliseconds = DefaultTimeout, [CallerMemberName] string memberName = null, [CallerFilePath] string filePath = null, [CallerLineNumber] int? lineNumber = null)
{
return OrTimeout(task, new TimeSpan(0, 0, 0, 0, milliseconds), memberName, filePath, lineNumber);
}
public static Task<T> OrTimeout<T>(this Task<T> task, TimeSpan timeout, [CallerMemberName] string memberName = null, [CallerFilePath] string filePath = null, [CallerLineNumber] int? lineNumber = null)
{
return task.TimeoutAfter(timeout, filePath, lineNumber ?? 0);
}
public static async Task OrThrowIfOtherFails(this Task task, Task otherTask)
{
var completed = await Task.WhenAny(task, otherTask);
if (completed == otherTask && otherTask.IsFaulted)
{
// Manifest the exception
otherTask.GetAwaiter().GetResult();
throw new Exception("Unreachable code");
}
else
{
// Await the task we were asked to await. Either it's finished, or the otherTask finished successfully, and it's not our job to check that
await task;
}
}
public static async Task<T> OrThrowIfOtherFails<T>(this Task<T> task, Task otherTask)
{
await OrThrowIfOtherFails((Task)task, otherTask);
// If we get here, 'task' is finished and succeeded.
return task.GetAwaiter().GetResult();
}
}
}

3
src/Middleware/build.cmd Normal file
View File

@ -0,0 +1,3 @@
@ECHO OFF
SET RepoRoot=%~dp0..\..
%RepoRoot%\build.cmd -projects %~dp0\**\*.*proj %*