diff --git a/eng/ProjectReferences.props b/eng/ProjectReferences.props
index 092cfa3472..0ac8db756b 100644
--- a/eng/ProjectReferences.props
+++ b/eng/ProjectReferences.props
@@ -83,6 +83,7 @@
+
diff --git a/src/Middleware/Middleware.sln b/src/Middleware/Middleware.sln
index 5f16259900..a02c8c40a8 100644
--- a/src/Middleware/Middleware.sln
+++ b/src/Middleware/Middleware.sln
@@ -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}
diff --git a/src/Middleware/RequestThrottling/RequestThrottling.slnf b/src/Middleware/RequestThrottling/RequestThrottling.slnf
new file mode 100644
index 0000000000..d434fbc862
--- /dev/null
+++ b/src/Middleware/RequestThrottling/RequestThrottling.slnf
@@ -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"
+ ]
+ }
+}
\ No newline at end of file
diff --git a/src/Middleware/RequestThrottling/ref/Microsoft.AspNetCore.RequestThrottling.csproj b/src/Middleware/RequestThrottling/ref/Microsoft.AspNetCore.RequestThrottling.csproj
new file mode 100644
index 0000000000..0a1bcdd0b9
--- /dev/null
+++ b/src/Middleware/RequestThrottling/ref/Microsoft.AspNetCore.RequestThrottling.csproj
@@ -0,0 +1,10 @@
+
+
+
+ netcoreapp3.0
+
+
+
+
+
+
diff --git a/src/Middleware/RequestThrottling/ref/Microsoft.AspNetCore.RequestThrottling.netcoreapp3.0.cs b/src/Middleware/RequestThrottling/ref/Microsoft.AspNetCore.RequestThrottling.netcoreapp3.0.cs
new file mode 100644
index 0000000000..618082bc4a
--- /dev/null
+++ b/src/Middleware/RequestThrottling/ref/Microsoft.AspNetCore.RequestThrottling.netcoreapp3.0.cs
@@ -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.
+
diff --git a/src/Middleware/RequestThrottling/sample/RequestThrottlingSample.csproj b/src/Middleware/RequestThrottling/sample/RequestThrottlingSample.csproj
new file mode 100644
index 0000000000..0f80e6516a
--- /dev/null
+++ b/src/Middleware/RequestThrottling/sample/RequestThrottlingSample.csproj
@@ -0,0 +1,13 @@
+
+
+
+ netcoreapp3.0
+
+
+
+
+
+
+
+
+
diff --git a/src/Middleware/RequestThrottling/sample/Startup.cs b/src/Middleware/RequestThrottling/sample/Startup.cs
new file mode 100644
index 0000000000..95a94be56d
--- /dev/null
+++ b/src/Middleware/RequestThrottling/sample/Startup.cs
@@ -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()
+ .Build();
+
+ host.Run();
+ }
+ }
+}
diff --git a/src/Middleware/RequestThrottling/src/Microsoft.AspNetCore.RequestThrottling.csproj b/src/Middleware/RequestThrottling/src/Microsoft.AspNetCore.RequestThrottling.csproj
new file mode 100644
index 0000000000..5014e9cec5
--- /dev/null
+++ b/src/Middleware/RequestThrottling/src/Microsoft.AspNetCore.RequestThrottling.csproj
@@ -0,0 +1,10 @@
+
+
+
+ ASP.NET Core middleware for queuing incoming HTTP requests, to avoid threadpool starvation.
+ netcoreapp3.0
+ true
+ aspnetcore;queue;queuing
+
+
+
diff --git a/src/Middleware/RequestThrottling/src/Properties/AssemblyInfo.cs b/src/Middleware/RequestThrottling/src/Properties/AssemblyInfo.cs
new file mode 100644
index 0000000000..1dcaedfaa6
--- /dev/null
+++ b/src/Middleware/RequestThrottling/src/Properties/AssemblyInfo.cs
@@ -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")]
diff --git a/src/Middleware/RequestThrottling/src/SemaphoreWrapper.cs b/src/Middleware/RequestThrottling/src/SemaphoreWrapper.cs
new file mode 100644
index 0000000000..4c79b94777
--- /dev/null
+++ b/src/Middleware/RequestThrottling/src/SemaphoreWrapper.cs
@@ -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();
+ }
+ }
+}
diff --git a/src/Middleware/RequestThrottling/test/Microsoft.AspNetCore.RequestThrottling.Tests.csproj b/src/Middleware/RequestThrottling/test/Microsoft.AspNetCore.RequestThrottling.Tests.csproj
new file mode 100644
index 0000000000..8c0dd8e989
--- /dev/null
+++ b/src/Middleware/RequestThrottling/test/Microsoft.AspNetCore.RequestThrottling.Tests.csproj
@@ -0,0 +1,10 @@
+
+
+
+ netcoreapp3.0
+
+
+
+
+
+
diff --git a/src/Middleware/RequestThrottling/test/SemaphoreWrapperTests.cs b/src/Middleware/RequestThrottling/test/SemaphoreWrapperTests.cs
new file mode 100644
index 0000000000..b5cdfce18f
--- /dev/null
+++ b/src/Middleware/RequestThrottling/test/SemaphoreWrapperTests.cs
@@ -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();
+ }
+ }
+}
diff --git a/src/Middleware/RequestThrottling/test/TaskExtensions.cs b/src/Middleware/RequestThrottling/test/TaskExtensions.cs
new file mode 100644
index 0000000000..52ec0c4303
--- /dev/null
+++ b/src/Middleware/RequestThrottling/test/TaskExtensions.cs
@@ -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 OrTimeout(this ValueTask 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 OrTimeout(this ValueTask 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 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 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 OrThrowIfOtherFails(this Task task, Task otherTask)
+ {
+ await OrThrowIfOtherFails((Task)task, otherTask);
+
+ // If we get here, 'task' is finished and succeeded.
+ return task.GetAwaiter().GetResult();
+ }
+ }
+}
diff --git a/src/Middleware/build.cmd b/src/Middleware/build.cmd
new file mode 100644
index 0000000000..033fe6f614
--- /dev/null
+++ b/src/Middleware/build.cmd
@@ -0,0 +1,3 @@
+@ECHO OFF
+SET RepoRoot=%~dp0..\..
+%RepoRoot%\build.cmd -projects %~dp0\**\*.*proj %*