diff --git a/src/Servers/Connections.Abstractions/src/IMemoryPoolFactory.cs b/src/Servers/Connections.Abstractions/src/IMemoryPoolFactory.cs
new file mode 100644
index 000000000000..16ffcee42687
--- /dev/null
+++ b/src/Servers/Connections.Abstractions/src/IMemoryPoolFactory.cs
@@ -0,0 +1,18 @@
+// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+
+using System.Buffers;
+
+namespace Microsoft.AspNetCore.Connections;
+
+///
+/// Interface for creating memory pools.
+///
+public interface IMemoryPoolFactory
+{
+ ///
+ /// Creates a new instance of a memory pool.
+ ///
+ /// A new memory pool instance.
+ MemoryPool Create();
+}
diff --git a/src/Servers/Connections.Abstractions/src/PublicAPI/net10.0/PublicAPI.Unshipped.txt b/src/Servers/Connections.Abstractions/src/PublicAPI/net10.0/PublicAPI.Unshipped.txt
index 7dc5c58110bf..dec7f8f71c13 100644
--- a/src/Servers/Connections.Abstractions/src/PublicAPI/net10.0/PublicAPI.Unshipped.txt
+++ b/src/Servers/Connections.Abstractions/src/PublicAPI/net10.0/PublicAPI.Unshipped.txt
@@ -1 +1,3 @@
#nullable enable
+Microsoft.AspNetCore.Connections.IMemoryPoolFactory
+Microsoft.AspNetCore.Connections.IMemoryPoolFactory.Create() -> System.Buffers.MemoryPool!
diff --git a/src/Servers/Connections.Abstractions/src/PublicAPI/net462/PublicAPI.Unshipped.txt b/src/Servers/Connections.Abstractions/src/PublicAPI/net462/PublicAPI.Unshipped.txt
index 7dc5c58110bf..dec7f8f71c13 100644
--- a/src/Servers/Connections.Abstractions/src/PublicAPI/net462/PublicAPI.Unshipped.txt
+++ b/src/Servers/Connections.Abstractions/src/PublicAPI/net462/PublicAPI.Unshipped.txt
@@ -1 +1,3 @@
#nullable enable
+Microsoft.AspNetCore.Connections.IMemoryPoolFactory
+Microsoft.AspNetCore.Connections.IMemoryPoolFactory.Create() -> System.Buffers.MemoryPool!
diff --git a/src/Servers/Connections.Abstractions/src/PublicAPI/netstandard2.0/PublicAPI.Unshipped.txt b/src/Servers/Connections.Abstractions/src/PublicAPI/netstandard2.0/PublicAPI.Unshipped.txt
index 7dc5c58110bf..dec7f8f71c13 100644
--- a/src/Servers/Connections.Abstractions/src/PublicAPI/netstandard2.0/PublicAPI.Unshipped.txt
+++ b/src/Servers/Connections.Abstractions/src/PublicAPI/netstandard2.0/PublicAPI.Unshipped.txt
@@ -1 +1,3 @@
#nullable enable
+Microsoft.AspNetCore.Connections.IMemoryPoolFactory
+Microsoft.AspNetCore.Connections.IMemoryPoolFactory.Create() -> System.Buffers.MemoryPool!
diff --git a/src/Servers/Connections.Abstractions/src/PublicAPI/netstandard2.1/PublicAPI.Unshipped.txt b/src/Servers/Connections.Abstractions/src/PublicAPI/netstandard2.1/PublicAPI.Unshipped.txt
index 7dc5c58110bf..dec7f8f71c13 100644
--- a/src/Servers/Connections.Abstractions/src/PublicAPI/netstandard2.1/PublicAPI.Unshipped.txt
+++ b/src/Servers/Connections.Abstractions/src/PublicAPI/netstandard2.1/PublicAPI.Unshipped.txt
@@ -1 +1,3 @@
#nullable enable
+Microsoft.AspNetCore.Connections.IMemoryPoolFactory
+Microsoft.AspNetCore.Connections.IMemoryPoolFactory.Create() -> System.Buffers.MemoryPool!
diff --git a/src/Servers/HttpSys/src/HttpSysListener.cs b/src/Servers/HttpSys/src/HttpSysListener.cs
index 933c627cd56e..c84a86382198 100644
--- a/src/Servers/HttpSys/src/HttpSysListener.cs
+++ b/src/Servers/HttpSys/src/HttpSysListener.cs
@@ -3,6 +3,7 @@
using System.Buffers;
using System.Diagnostics;
+using Microsoft.AspNetCore.Connections;
using Microsoft.AspNetCore.Http;
using Microsoft.AspNetCore.HttpSys.Internal;
using Microsoft.AspNetCore.WebUtilities;
@@ -33,7 +34,7 @@ internal sealed partial class HttpSysListener : IDisposable
// 0.5 seconds per request. Respond with a 400 Bad Request.
private const int UnknownHeaderLimit = 1000;
- internal MemoryPool MemoryPool { get; } = PinnedBlockMemoryPoolFactory.Create();
+ internal MemoryPool MemoryPool { get; }
private volatile State _state; // m_State is set only within lock blocks, but often read outside locks.
@@ -44,7 +45,7 @@ internal sealed partial class HttpSysListener : IDisposable
private readonly object _internalLock;
- public HttpSysListener(HttpSysOptions options, ILoggerFactory loggerFactory)
+ public HttpSysListener(HttpSysOptions options, IMemoryPoolFactory memoryPoolFactory, ILoggerFactory loggerFactory)
{
ArgumentNullException.ThrowIfNull(options);
ArgumentNullException.ThrowIfNull(loggerFactory);
@@ -54,6 +55,8 @@ public HttpSysListener(HttpSysOptions options, ILoggerFactory loggerFactory)
throw new PlatformNotSupportedException();
}
+ MemoryPool = memoryPoolFactory.Create();
+
Options = options;
Logger = loggerFactory.CreateLogger();
diff --git a/src/Servers/HttpSys/src/MessagePump.cs b/src/Servers/HttpSys/src/MessagePump.cs
index 695c45d3b4e9..ae0a8a5a66cc 100644
--- a/src/Servers/HttpSys/src/MessagePump.cs
+++ b/src/Servers/HttpSys/src/MessagePump.cs
@@ -4,6 +4,7 @@
using System.Diagnostics;
using System.Linq;
using Microsoft.AspNetCore.Authentication;
+using Microsoft.AspNetCore.Connections;
using Microsoft.AspNetCore.Hosting.Server;
using Microsoft.AspNetCore.Hosting.Server.Features;
using Microsoft.AspNetCore.Http.Features;
@@ -27,12 +28,13 @@ internal sealed partial class MessagePump : IServer, IServerDelegationFeature
private readonly ServerAddressesFeature _serverAddresses;
- public MessagePump(IOptions options, ILoggerFactory loggerFactory, IAuthenticationSchemeProvider authentication)
+ public MessagePump(IOptions options, IMemoryPoolFactory memoryPoolFactory,
+ ILoggerFactory loggerFactory, IAuthenticationSchemeProvider authentication)
{
ArgumentNullException.ThrowIfNull(options);
ArgumentNullException.ThrowIfNull(loggerFactory);
_options = options.Value;
- Listener = new HttpSysListener(_options, loggerFactory);
+ Listener = new HttpSysListener(_options, memoryPoolFactory, loggerFactory);
_logger = loggerFactory.CreateLogger();
if (_options.Authentication.Schemes != AuthenticationSchemes.None)
diff --git a/src/Servers/HttpSys/src/WebHostBuilderHttpSysExtensions.cs b/src/Servers/HttpSys/src/WebHostBuilderHttpSysExtensions.cs
index fb0feaa23609..cf768cc64469 100644
--- a/src/Servers/HttpSys/src/WebHostBuilderHttpSysExtensions.cs
+++ b/src/Servers/HttpSys/src/WebHostBuilderHttpSysExtensions.cs
@@ -2,9 +2,11 @@
// The .NET Foundation licenses this file to you under the MIT license.
using System.Runtime.Versioning;
+using Microsoft.AspNetCore.Connections;
using Microsoft.AspNetCore.Hosting.Server;
using Microsoft.AspNetCore.Server.HttpSys;
using Microsoft.Extensions.DependencyInjection;
+using Microsoft.Extensions.DependencyInjection.Extensions;
using Microsoft.Extensions.Options;
namespace Microsoft.AspNetCore.Hosting;
@@ -45,6 +47,8 @@ public static IWebHostBuilder UseHttpSys(this IWebHostBuilder hostBuilder)
};
});
services.AddAuthenticationCore();
+
+ services.TryAddSingleton, DefaultMemoryPoolFactory>();
});
}
diff --git a/src/Servers/HttpSys/test/FunctionalTests/Listener/ServerTests.cs b/src/Servers/HttpSys/test/FunctionalTests/Listener/ServerTests.cs
index 8a7a9d4da76e..d961ce3a0353 100644
--- a/src/Servers/HttpSys/test/FunctionalTests/Listener/ServerTests.cs
+++ b/src/Servers/HttpSys/test/FunctionalTests/Listener/ServerTests.cs
@@ -2,6 +2,7 @@
// The .NET Foundation licenses this file to you under the MIT license.
using System;
+using System.Buffers;
using System.IO;
using System.Net;
using System.Net.Http;
@@ -132,7 +133,7 @@ public void Server_RegisterUnavailablePrefix_ThrowsActionableHttpSysException()
var options = new HttpSysOptions();
options.UrlPrefixes.Add(address1);
- using var listener = new HttpSysListener(options, new LoggerFactory());
+ using var listener = new HttpSysListener(options, new DefaultMemoryPoolFactory(), new LoggerFactory());
var exception = Assert.Throws(() => listener.Start());
diff --git a/src/Servers/HttpSys/test/FunctionalTests/Listener/Utilities.cs b/src/Servers/HttpSys/test/FunctionalTests/Listener/Utilities.cs
index 409faaa5b13d..5e9cdadfdb62 100644
--- a/src/Servers/HttpSys/test/FunctionalTests/Listener/Utilities.cs
+++ b/src/Servers/HttpSys/test/FunctionalTests/Listener/Utilities.cs
@@ -2,6 +2,7 @@
// The .NET Foundation licenses this file to you under the MIT license.
using System;
+using System.Buffers;
using System.Threading.Tasks;
using Microsoft.AspNetCore.HttpSys.Internal;
using Microsoft.Extensions.Logging;
@@ -47,7 +48,7 @@ internal static HttpSysListener CreateDynamicHttpServer(string basePath, out str
var options = new HttpSysOptions();
options.UrlPrefixes.Add(prefix);
options.RequestQueueName = prefix.Port; // Convention for use with CreateServerOnExistingQueue
- var listener = new HttpSysListener(options, new LoggerFactory());
+ var listener = new HttpSysListener(options, new DefaultMemoryPoolFactory(), new LoggerFactory());
try
{
listener.Start();
@@ -76,7 +77,7 @@ internal static HttpSysListener CreateHttpsServer()
internal static HttpSysListener CreateServer(string scheme, string host, int port, string path)
{
- var listener = new HttpSysListener(new HttpSysOptions(), new LoggerFactory());
+ var listener = new HttpSysListener(new HttpSysOptions(), new DefaultMemoryPoolFactory(), new LoggerFactory());
listener.Options.UrlPrefixes.Add(UrlPrefix.Create(scheme, host, port, path));
listener.Start();
return listener;
@@ -86,7 +87,7 @@ internal static HttpSysListener CreateServer(Action configureOpt
{
var options = new HttpSysOptions();
configureOptions(options);
- var listener = new HttpSysListener(options, new LoggerFactory());
+ var listener = new HttpSysListener(options, new DefaultMemoryPoolFactory(), new LoggerFactory());
listener.Start();
return listener;
}
diff --git a/src/Servers/HttpSys/test/FunctionalTests/Utilities.cs b/src/Servers/HttpSys/test/FunctionalTests/Utilities.cs
index ef28510f1dac..c1b17c72fc59 100644
--- a/src/Servers/HttpSys/test/FunctionalTests/Utilities.cs
+++ b/src/Servers/HttpSys/test/FunctionalTests/Utilities.cs
@@ -2,6 +2,7 @@
// The .NET Foundation licenses this file to you under the MIT license.
using System;
+using System.Buffers;
using System.Linq;
using System.Threading;
using System.Threading.Tasks;
@@ -112,13 +113,13 @@ internal static IHost CreateDynamicHost(string basePath, out string root, out st
}
internal static MessagePump CreatePump(ILoggerFactory loggerFactory)
- => new MessagePump(Options.Create(new HttpSysOptions()), loggerFactory ?? new LoggerFactory(), new AuthenticationSchemeProvider(Options.Create(new AuthenticationOptions())));
+ => new MessagePump(Options.Create(new HttpSysOptions()), new DefaultMemoryPoolFactory(), loggerFactory ?? new LoggerFactory(), new AuthenticationSchemeProvider(Options.Create(new AuthenticationOptions())));
internal static MessagePump CreatePump(Action configureOptions, ILoggerFactory loggerFactory)
{
var options = new HttpSysOptions();
configureOptions(options);
- return new MessagePump(Options.Create(options), loggerFactory ?? new LoggerFactory(), new AuthenticationSchemeProvider(Options.Create(new AuthenticationOptions())));
+ return new MessagePump(Options.Create(options), new DefaultMemoryPoolFactory(), loggerFactory ?? new LoggerFactory(), new AuthenticationSchemeProvider(Options.Create(new AuthenticationOptions())));
}
internal static IServer CreateDynamicHttpServer(string basePath, out string root, out string baseAddress, Action configureOptions, RequestDelegate app, ILoggerFactory loggerFactory)
diff --git a/src/Servers/HttpSys/test/NonHelixTests/Utilities.cs b/src/Servers/HttpSys/test/NonHelixTests/Utilities.cs
index aa5880561e54..a276f56d93ca 100644
--- a/src/Servers/HttpSys/test/NonHelixTests/Utilities.cs
+++ b/src/Servers/HttpSys/test/NonHelixTests/Utilities.cs
@@ -1,6 +1,7 @@
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.
+using System.Buffers;
using Microsoft.AspNetCore.Authentication;
using Microsoft.AspNetCore.Hosting.Server;
using Microsoft.AspNetCore.Hosting.Server.Features;
@@ -31,13 +32,13 @@ internal static IServer CreateHttpServer(out string baseAddress, RequestDelegate
}
internal static MessagePump CreatePump(ILoggerFactory loggerFactory = null)
- => new MessagePump(Options.Create(new HttpSysOptions()), loggerFactory ?? new LoggerFactory(), new AuthenticationSchemeProvider(Options.Create(new AuthenticationOptions())));
+ => new MessagePump(Options.Create(new HttpSysOptions()), new DefaultMemoryPoolFactory(), loggerFactory ?? new LoggerFactory(), new AuthenticationSchemeProvider(Options.Create(new AuthenticationOptions())));
internal static MessagePump CreatePump(Action configureOptions, ILoggerFactory loggerFactory = null)
{
var options = new HttpSysOptions();
configureOptions(options);
- return new MessagePump(Options.Create(options), loggerFactory ?? new LoggerFactory(), new AuthenticationSchemeProvider(Options.Create(new AuthenticationOptions())));
+ return new MessagePump(Options.Create(options), new DefaultMemoryPoolFactory(), loggerFactory ?? new LoggerFactory(), new AuthenticationSchemeProvider(Options.Create(new AuthenticationOptions())));
}
internal static IServer CreateDynamicHttpServer(string basePath, out string root, out string baseAddress, Action configureOptions, RequestDelegate app)
diff --git a/src/Servers/IIS/IIS/src/Core/IISHttpServer.cs b/src/Servers/IIS/IIS/src/Core/IISHttpServer.cs
index 6abaa5b2c0db..df0484310b0e 100644
--- a/src/Servers/IIS/IIS/src/Core/IISHttpServer.cs
+++ b/src/Servers/IIS/IIS/src/Core/IISHttpServer.cs
@@ -6,6 +6,7 @@
using System.Runtime.InteropServices;
using Microsoft.AspNetCore.Authentication;
using Microsoft.AspNetCore.Builder;
+using Microsoft.AspNetCore.Connections;
using Microsoft.AspNetCore.Hosting.Server;
using Microsoft.AspNetCore.Hosting.Server.Features;
using Microsoft.AspNetCore.Http.Features;
@@ -21,7 +22,7 @@ internal sealed class IISHttpServer : IServer
private const string WebSocketVersionString = "WEBSOCKET_VERSION";
private IISContextFactory? _iisContextFactory;
- private readonly MemoryPool _memoryPool = new PinnedBlockMemoryPool();
+ private readonly MemoryPool _memoryPool;
private GCHandle _httpServerHandle;
private readonly IHostApplicationLifetime _applicationLifetime;
private readonly ILogger _logger;
@@ -60,10 +61,12 @@ public IISHttpServer(
IHostApplicationLifetime applicationLifetime,
IAuthenticationSchemeProvider authentication,
IConfiguration configuration,
+ IMemoryPoolFactory memoryPoolFactory,
IOptions options,
ILogger logger
)
{
+ _memoryPool = memoryPoolFactory.Create();
_nativeApplication = nativeApplication;
_applicationLifetime = applicationLifetime;
_logger = logger;
diff --git a/src/Servers/IIS/IIS/src/WebHostBuilderIISExtensions.cs b/src/Servers/IIS/IIS/src/WebHostBuilderIISExtensions.cs
index 302f1af5a6c4..b151931e0ae6 100644
--- a/src/Servers/IIS/IIS/src/WebHostBuilderIISExtensions.cs
+++ b/src/Servers/IIS/IIS/src/WebHostBuilderIISExtensions.cs
@@ -2,10 +2,12 @@
// The .NET Foundation licenses this file to you under the MIT license.
using Microsoft.AspNetCore.Builder;
+using Microsoft.AspNetCore.Connections;
using Microsoft.AspNetCore.Hosting.Server;
using Microsoft.AspNetCore.Server.IIS;
using Microsoft.AspNetCore.Server.IIS.Core;
using Microsoft.Extensions.DependencyInjection;
+using Microsoft.Extensions.DependencyInjection.Extensions;
namespace Microsoft.AspNetCore.Hosting;
@@ -53,6 +55,8 @@ public static IWebHostBuilder UseIIS(this IWebHostBuilder hostBuilder)
options.IisMaxRequestSizeLimit = iisConfigData.maxRequestBodySize;
}
);
+
+ services.TryAddSingleton, DefaultMemoryPoolFactory>();
});
}
diff --git a/src/Servers/Kestrel/Core/src/Internal/KestrelServerImpl.cs b/src/Servers/Kestrel/Core/src/Internal/KestrelServerImpl.cs
index 9324e481104c..ab03c06fcaaf 100644
--- a/src/Servers/Kestrel/Core/src/Internal/KestrelServerImpl.cs
+++ b/src/Servers/Kestrel/Core/src/Internal/KestrelServerImpl.cs
@@ -40,8 +40,9 @@ public KestrelServerImpl(
IHttpsConfigurationService httpsConfigurationService,
ILoggerFactory loggerFactory,
DiagnosticSource? diagnosticSource,
- KestrelMetrics metrics)
- : this(transportFactories, multiplexedFactories, httpsConfigurationService, CreateServiceContext(options, loggerFactory, diagnosticSource, metrics))
+ KestrelMetrics metrics,
+ IEnumerable heartbeatHandlers)
+ : this(transportFactories, multiplexedFactories, httpsConfigurationService, CreateServiceContext(options, loggerFactory, diagnosticSource, metrics, heartbeatHandlers))
{
}
@@ -73,7 +74,8 @@ internal KestrelServerImpl(
_transportManager = new TransportManager(_transportFactories, _multiplexedTransportFactories, _httpsConfigurationService, ServiceContext);
}
- private static ServiceContext CreateServiceContext(IOptions options, ILoggerFactory loggerFactory, DiagnosticSource? diagnosticSource, KestrelMetrics metrics)
+ private static ServiceContext CreateServiceContext(IOptions options, ILoggerFactory loggerFactory, DiagnosticSource? diagnosticSource, KestrelMetrics metrics,
+ IEnumerable heartbeatHandlers)
{
ArgumentNullException.ThrowIfNull(options);
ArgumentNullException.ThrowIfNull(loggerFactory);
@@ -87,7 +89,7 @@ private static ServiceContext CreateServiceContext(IOptions, IHeartbeatHandler
+{
+ private readonly IMeterFactory _meterFactory;
+ private readonly ILogger? _logger;
+ private readonly TimeProvider _timeProvider;
+ // micro-optimization: Using nuint as the value type to avoid GC write barriers; could replace with ConcurrentHashSet if that becomes available
+ private readonly ConcurrentDictionary _pools = new();
+
+ public PinnedBlockMemoryPoolFactory(IMeterFactory meterFactory, TimeProvider? timeProvider = null, ILogger? logger = null)
+ {
+ _timeProvider = timeProvider ?? TimeProvider.System;
+ _meterFactory = meterFactory;
+ _logger = logger;
+ }
+
+ public MemoryPool Create()
+ {
+ var pool = new PinnedBlockMemoryPool(_meterFactory, _logger);
+
+ _pools.TryAdd(pool, nuint.Zero);
+
+ pool.OnPoolDisposed(static (state, self) =>
+ {
+ ((ConcurrentDictionary)state!).TryRemove(self, out _);
+ }, _pools);
+
+ return pool;
+ }
+
+ public void OnHeartbeat()
+ {
+ var now = _timeProvider.GetUtcNow();
+ foreach (var pool in _pools)
+ {
+ pool.Key.TryScheduleEviction(now);
+ }
+ }
+}
diff --git a/src/Servers/Kestrel/Core/src/KestrelServer.cs b/src/Servers/Kestrel/Core/src/KestrelServer.cs
index 7f2909c77cf6..3b91510218f3 100644
--- a/src/Servers/Kestrel/Core/src/KestrelServer.cs
+++ b/src/Servers/Kestrel/Core/src/KestrelServer.cs
@@ -37,7 +37,8 @@ public KestrelServer(IOptions options, IConnectionListener
new SimpleHttpsConfigurationService(),
loggerFactory,
diagnosticSource: null,
- new KestrelMetrics(new DummyMeterFactory()));
+ new KestrelMetrics(new DummyMeterFactory()),
+ heartbeatHandlers: []);
}
///
diff --git a/src/Servers/Kestrel/Core/src/Microsoft.AspNetCore.Server.Kestrel.Core.csproj b/src/Servers/Kestrel/Core/src/Microsoft.AspNetCore.Server.Kestrel.Core.csproj
index 9199ae066c0a..c35c22dd8d94 100644
--- a/src/Servers/Kestrel/Core/src/Microsoft.AspNetCore.Server.Kestrel.Core.csproj
+++ b/src/Servers/Kestrel/Core/src/Microsoft.AspNetCore.Server.Kestrel.Core.csproj
@@ -1,4 +1,4 @@
-
+
Core components of ASP.NET Core Kestrel cross-platform web server.
@@ -37,6 +37,7 @@
+
diff --git a/src/Servers/Kestrel/Core/test/DiagnosticMemoryPoolTests.cs b/src/Servers/Kestrel/Core/test/DiagnosticMemoryPoolTests.cs
index 100f50ad7645..adceaa3a5c0e 100644
--- a/src/Servers/Kestrel/Core/test/DiagnosticMemoryPoolTests.cs
+++ b/src/Servers/Kestrel/Core/test/DiagnosticMemoryPoolTests.cs
@@ -1,10 +1,11 @@
-// Licensed to the .NET Foundation under one or more agreements.
+// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.
using System;
using System.Buffers;
using System.Runtime.InteropServices;
using System.Threading.Tasks;
+using Microsoft.AspNetCore;
using Xunit;
namespace Microsoft.Extensions.Internal.Test;
diff --git a/src/Servers/Kestrel/Core/test/Http1/Http1ConnectionTestsBase.cs b/src/Servers/Kestrel/Core/test/Http1/Http1ConnectionTestsBase.cs
index 3fd5631ed42c..4b460630c9f3 100644
--- a/src/Servers/Kestrel/Core/test/Http1/Http1ConnectionTestsBase.cs
+++ b/src/Servers/Kestrel/Core/test/Http1/Http1ConnectionTestsBase.cs
@@ -31,7 +31,7 @@ protected override void Initialize(TestContext context, MethodInfo methodInfo, o
{
base.Initialize(context, methodInfo, testMethodArguments, testOutputHelper);
- _pipelineFactory = PinnedBlockMemoryPoolFactory.Create();
+ _pipelineFactory = TestMemoryPoolFactory.Create();
var options = new PipeOptions(_pipelineFactory, readerScheduler: PipeScheduler.Inline, writerScheduler: PipeScheduler.Inline, useSynchronizationContext: false);
var pair = DuplexPipe.CreateConnectionPair(options, options);
diff --git a/src/Servers/Kestrel/Core/test/Http1/Http1OutputProducerTests.cs b/src/Servers/Kestrel/Core/test/Http1/Http1OutputProducerTests.cs
index b927baefa60f..0683b9ea6c84 100644
--- a/src/Servers/Kestrel/Core/test/Http1/Http1OutputProducerTests.cs
+++ b/src/Servers/Kestrel/Core/test/Http1/Http1OutputProducerTests.cs
@@ -22,7 +22,7 @@ public class Http1OutputProducerTests : IDisposable
public Http1OutputProducerTests()
{
- _memoryPool = PinnedBlockMemoryPoolFactory.Create();
+ _memoryPool = TestMemoryPoolFactory.Create();
}
public void Dispose()
diff --git a/src/Servers/Kestrel/Core/test/HttpResponseHeadersTests.cs b/src/Servers/Kestrel/Core/test/HttpResponseHeadersTests.cs
index a68adb6daf5b..e2105939f504 100644
--- a/src/Servers/Kestrel/Core/test/HttpResponseHeadersTests.cs
+++ b/src/Servers/Kestrel/Core/test/HttpResponseHeadersTests.cs
@@ -21,7 +21,7 @@ public class HttpResponseHeadersTests
[Fact]
public void InitialDictionaryIsEmpty()
{
- using (var memoryPool = PinnedBlockMemoryPoolFactory.Create())
+ using (var memoryPool = TestMemoryPoolFactory.Create())
{
var options = new PipeOptions(memoryPool, readerScheduler: PipeScheduler.Inline, writerScheduler: PipeScheduler.Inline, useSynchronizationContext: false);
var pair = DuplexPipe.CreateConnectionPair(options, options);
diff --git a/src/Servers/Kestrel/Core/test/KestrelServerTests.cs b/src/Servers/Kestrel/Core/test/KestrelServerTests.cs
index e688812a6075..be0c0de49cd5 100644
--- a/src/Servers/Kestrel/Core/test/KestrelServerTests.cs
+++ b/src/Servers/Kestrel/Core/test/KestrelServerTests.cs
@@ -310,7 +310,8 @@ private static KestrelServerImpl CreateKestrelServer(
httpsConfigurationService,
loggerFactory ?? new LoggerFactory(new[] { new KestrelTestLoggerProvider() }),
diagnosticSource: null,
- metrics ?? new KestrelMetrics(new TestMeterFactory()));
+ metrics ?? new KestrelMetrics(new TestMeterFactory()),
+ heartbeatHandlers: []);
}
[Fact]
diff --git a/src/Servers/Kestrel/Core/test/Microsoft.AspNetCore.Server.Kestrel.Core.Tests.csproj b/src/Servers/Kestrel/Core/test/Microsoft.AspNetCore.Server.Kestrel.Core.Tests.csproj
index 509856074d7c..37aea90bcf3c 100644
--- a/src/Servers/Kestrel/Core/test/Microsoft.AspNetCore.Server.Kestrel.Core.Tests.csproj
+++ b/src/Servers/Kestrel/Core/test/Microsoft.AspNetCore.Server.Kestrel.Core.Tests.csproj
@@ -14,7 +14,6 @@
-
@@ -29,6 +28,7 @@
+
diff --git a/src/Servers/Kestrel/Core/test/PinnedBlockMemoryPoolFactoryTests.cs b/src/Servers/Kestrel/Core/test/PinnedBlockMemoryPoolFactoryTests.cs
new file mode 100644
index 000000000000..4d23015459eb
--- /dev/null
+++ b/src/Servers/Kestrel/Core/test/PinnedBlockMemoryPoolFactoryTests.cs
@@ -0,0 +1,113 @@
+// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+
+using System.Buffers;
+using System.Collections.Concurrent;
+using System.Reflection;
+using Microsoft.AspNetCore.InternalTesting;
+using Microsoft.AspNetCore.Server.Kestrel.Core.Internal;
+using Microsoft.Extensions.Time.Testing;
+
+namespace Microsoft.AspNetCore.Server.Kestrel.Core.Tests;
+
+public class PinnedBlockMemoryPoolFactoryTests
+{
+ [Fact]
+ public void CreatePool()
+ {
+ var factory = new PinnedBlockMemoryPoolFactory(new TestMeterFactory());
+ var pool = factory.Create();
+ Assert.NotNull(pool);
+ Assert.IsType(pool);
+ }
+
+ [Fact]
+ public void CreateMultiplePools()
+ {
+ var factory = new PinnedBlockMemoryPoolFactory(new TestMeterFactory());
+ var pool1 = factory.Create();
+ var pool2 = factory.Create();
+
+ Assert.NotNull(pool1);
+ Assert.NotNull(pool2);
+ Assert.NotSame(pool1, pool2);
+ }
+
+ [Fact]
+ public void DisposePoolRemovesFromFactory()
+ {
+ var factory = new PinnedBlockMemoryPoolFactory(new TestMeterFactory());
+ var pool = factory.Create();
+ Assert.NotNull(pool);
+
+ var dict = (ConcurrentDictionary)(typeof(PinnedBlockMemoryPoolFactory)
+ .GetField("_pools", BindingFlags.NonPublic | BindingFlags.Instance)
+ ?.GetValue(factory));
+ Assert.Single(dict);
+
+ pool.Dispose();
+ Assert.Empty(dict);
+ }
+
+ [Fact]
+ public async Task FactoryHeartbeatWorks()
+ {
+ var timeProvider = new FakeTimeProvider(DateTimeOffset.UtcNow.AddDays(1));
+ var factory = new PinnedBlockMemoryPoolFactory(new TestMeterFactory(), timeProvider);
+
+ // Use 2 pools to make sure they all get triggered by the heartbeat
+ var pool = Assert.IsType(factory.Create());
+ var pool2 = Assert.IsType(factory.Create());
+
+ var blocks = new List>();
+ for (var i = 0; i < 10000; i++)
+ {
+ blocks.Add(pool.Rent());
+ blocks.Add(pool2.Rent());
+ }
+
+ foreach (var block in blocks)
+ {
+ block.Dispose();
+ }
+ blocks.Clear();
+
+ // First eviction pass likely won't do anything since the pool was just very active
+ factory.OnHeartbeat();
+
+ var previousCount = pool.BlockCount();
+ var previousCount2 = pool2.BlockCount();
+ timeProvider.Advance(TimeSpan.FromSeconds(10));
+ factory.OnHeartbeat();
+
+ await VerifyPoolEviction(pool, previousCount);
+ await VerifyPoolEviction(pool2, previousCount2);
+
+ timeProvider.Advance(TimeSpan.FromSeconds(10));
+
+ previousCount = pool.BlockCount();
+ previousCount2 = pool2.BlockCount();
+ factory.OnHeartbeat();
+
+ await VerifyPoolEviction(pool, previousCount);
+ await VerifyPoolEviction(pool2, previousCount2);
+
+ static async Task VerifyPoolEviction(PinnedBlockMemoryPool pool, int previousCount)
+ {
+ // Because the eviction happens on a thread pool thread, we need to wait for it to complete
+ // and the only way to do that (without adding a test hook in the pool code) is to delay.
+ // But we don't want to add an arbitrary delay, so we do a short delay with block count checks
+ // to reduce the wait time.
+ var maxWait = TimeSpan.FromSeconds(5);
+ while (pool.BlockCount() > previousCount - (previousCount / 30) && maxWait > TimeSpan.Zero)
+ {
+ await Task.Delay(50);
+ maxWait -= TimeSpan.FromMilliseconds(50);
+ }
+
+ // Assert that the block count has decreased by 3.3-10%.
+ // This relies on the current implementation of eviction logic which may change in the future.
+ Assert.InRange(pool.BlockCount(), previousCount - (previousCount / 10), previousCount - (previousCount / 30));
+ }
+ }
+}
diff --git a/src/Servers/Kestrel/Core/test/PinnedBlockMemoryPoolTests.cs b/src/Servers/Kestrel/Core/test/PinnedBlockMemoryPoolTests.cs
index 30d1f1c799a4..f35fd05c36ef 100644
--- a/src/Servers/Kestrel/Core/test/PinnedBlockMemoryPoolTests.cs
+++ b/src/Servers/Kestrel/Core/test/PinnedBlockMemoryPoolTests.cs
@@ -1,8 +1,12 @@
-// Licensed to the .NET Foundation under one or more agreements.
+// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.
using System.Buffers;
-using Xunit;
+using Microsoft.AspNetCore;
+using Microsoft.AspNetCore.InternalTesting;
+using Microsoft.AspNetCore.Server.Kestrel.Core.Internal;
+using Microsoft.Extensions.Diagnostics.Metrics.Testing;
+using Microsoft.Extensions.Time.Testing;
namespace Microsoft.Extensions.Internal.Test;
@@ -25,4 +29,350 @@ public void DisposeWithActiveBlocksWorks()
var block = memoryPool.Rent();
memoryPool.Dispose();
}
+
+ [Fact]
+ public void CanEvictBlocks()
+ {
+ using var memoryPool = new PinnedBlockMemoryPool();
+
+ var block = memoryPool.Rent();
+ block.Dispose();
+ Assert.Equal(1, memoryPool.BlockCount());
+
+ // First eviction does nothing because we double counted the initial rent due to it needing to allocate
+ memoryPool.PerformEviction();
+ Assert.Equal(1, memoryPool.BlockCount());
+
+ memoryPool.PerformEviction();
+ Assert.Equal(0, memoryPool.BlockCount());
+ }
+
+ [Fact]
+ public void EvictsSmallAmountOfBlocksWhenTrafficIsTheSame()
+ {
+ using var memoryPool = new PinnedBlockMemoryPool();
+
+ var blocks = new List>();
+ for (var i = 0; i < 10000; i++)
+ {
+ blocks.Add(memoryPool.Rent());
+ }
+ Assert.Equal(0, memoryPool.BlockCount());
+ memoryPool.PerformEviction();
+
+ foreach (var block in blocks)
+ {
+ block.Dispose();
+ }
+ blocks.Clear();
+ Assert.Equal(10000, memoryPool.BlockCount());
+ memoryPool.PerformEviction();
+
+ var originalCount = memoryPool.BlockCount();
+ for (var j = 0; j < 100; j++)
+ {
+ var previousCount = memoryPool.BlockCount();
+ // Rent and return at the same rate
+ for (var i = 0; i < 100; i++)
+ {
+ blocks.Add(memoryPool.Rent());
+ }
+ foreach (var block in blocks)
+ {
+ block.Dispose();
+ }
+ blocks.Clear();
+
+ Assert.Equal(previousCount, memoryPool.BlockCount());
+
+ // Eviction while rent+return is the same
+ memoryPool.PerformEviction();
+ Assert.InRange(memoryPool.BlockCount(), previousCount - (previousCount / 100), previousCount - 1);
+ }
+
+ Assert.True(memoryPool.BlockCount() <= originalCount - 100, "Evictions should have removed some blocks");
+ }
+
+ [Fact]
+ public void DoesNotEvictBlocksWhenActive()
+ {
+ using var memoryPool = new PinnedBlockMemoryPool();
+
+ var blocks = new List>();
+ for (var i = 0; i < 10000; i++)
+ {
+ blocks.Add(memoryPool.Rent());
+ }
+ Assert.Equal(0, memoryPool.BlockCount());
+ memoryPool.PerformEviction();
+
+ foreach (var block in blocks)
+ {
+ block.Dispose();
+ }
+ blocks.Clear();
+ Assert.Equal(10000, memoryPool.BlockCount());
+ memoryPool.PerformEviction();
+ var previousCount = memoryPool.BlockCount();
+
+ // Simulate active usage, rent without returning
+ for (var i = 0; i < 100; i++)
+ {
+ blocks.Add(memoryPool.Rent());
+ }
+ previousCount -= 100;
+
+ // Eviction while pool is actively used should not remove blocks
+ memoryPool.PerformEviction();
+ Assert.Equal(previousCount, memoryPool.BlockCount());
+ }
+
+ [Fact]
+ public void EvictsBlocksGraduallyWhenIdle()
+ {
+ using var memoryPool = new PinnedBlockMemoryPool();
+
+ var blocks = new List>();
+ for (var i = 0; i < 10000; i++)
+ {
+ blocks.Add(memoryPool.Rent());
+ }
+ Assert.Equal(0, memoryPool.BlockCount());
+ memoryPool.PerformEviction();
+
+ foreach (var block in blocks)
+ {
+ block.Dispose();
+ }
+ blocks.Clear();
+ Assert.Equal(10000, memoryPool.BlockCount());
+ // Eviction after returning everything to reset internal counters
+ memoryPool.PerformEviction();
+
+ // Eviction should happen gradually over multiple calls
+ for (var i = 0; i < 10; i++)
+ {
+ var previousCount = memoryPool.BlockCount();
+ memoryPool.PerformEviction();
+ // Eviction while idle should remove 10-30% of blocks
+ Assert.InRange(memoryPool.BlockCount(), previousCount - (previousCount / 10), previousCount - (previousCount / 30));
+ }
+
+ // Ensure all blocks are evicted eventually
+ var count = memoryPool.BlockCount();
+ do
+ {
+ count = memoryPool.BlockCount();
+ memoryPool.PerformEviction();
+ }
+ // Make sure the loop makes forward progress
+ while (count != 0 && count != memoryPool.BlockCount());
+
+ Assert.Equal(0, memoryPool.BlockCount());
+ }
+
+ [Fact]
+ public async Task EvictionsAreScheduled()
+ {
+ using var memoryPool = new PinnedBlockMemoryPool();
+
+ var blocks = new List>();
+ for (var i = 0; i < 10000; i++)
+ {
+ blocks.Add(memoryPool.Rent());
+ }
+ Assert.Equal(0, memoryPool.BlockCount());
+
+ foreach (var block in blocks)
+ {
+ block.Dispose();
+ }
+ blocks.Clear();
+ Assert.Equal(10000, memoryPool.BlockCount());
+ // Eviction after returning everything to reset internal counters
+ memoryPool.PerformEviction();
+
+ Assert.Equal(10000, memoryPool.BlockCount());
+
+ var previousCount = memoryPool.BlockCount();
+
+ // Scheduling only works every 10 seconds and is initialized to UtcNow + 10 when the pool is constructed
+ var time = DateTime.UtcNow;
+ Assert.False(memoryPool.TryScheduleEviction(time));
+
+ Assert.True(memoryPool.TryScheduleEviction(time.AddSeconds(10)));
+
+ var maxWait = TimeSpan.FromSeconds(5);
+ while (memoryPool.BlockCount() > previousCount - (previousCount / 30) && maxWait > TimeSpan.Zero)
+ {
+ await Task.Delay(50);
+ maxWait -= TimeSpan.FromMilliseconds(50);
+ }
+
+ Assert.InRange(memoryPool.BlockCount(), previousCount - (previousCount / 10), previousCount - (previousCount / 30));
+
+ // Since we scheduled successfully, we now need to wait 10 seconds to schedule again.
+ Assert.False(memoryPool.TryScheduleEviction(time.AddSeconds(10)));
+
+ previousCount = memoryPool.BlockCount();
+ Assert.True(memoryPool.TryScheduleEviction(time.AddSeconds(20)));
+
+ maxWait = TimeSpan.FromSeconds(5);
+ while (memoryPool.BlockCount() > previousCount - (previousCount / 30) && maxWait > TimeSpan.Zero)
+ {
+ await Task.Delay(50);
+ maxWait -= TimeSpan.FromMilliseconds(50);
+ }
+
+ Assert.InRange(memoryPool.BlockCount(), previousCount - (previousCount / 10), previousCount - (previousCount / 30));
+ }
+
+ [Fact]
+ public void CurrentMemoryMetricTracksPooledMemory()
+ {
+ var testMeterFactory = new TestMeterFactory();
+ using var currentMemoryMetric = new MetricCollector(testMeterFactory, "Microsoft.AspNetCore.MemoryPool", "aspnetcore.memorypool.current_memory");
+
+ var pool = new PinnedBlockMemoryPool(testMeterFactory);
+
+ Assert.Empty(currentMemoryMetric.GetMeasurementSnapshot());
+
+ var mem = pool.Rent();
+ mem.Dispose();
+
+ Assert.Collection(currentMemoryMetric.GetMeasurementSnapshot(), m => Assert.Equal(PinnedBlockMemoryPool.BlockSize, m.Value));
+
+ mem = pool.Rent();
+
+ Assert.Equal(-1 * PinnedBlockMemoryPool.BlockSize, currentMemoryMetric.LastMeasurement.Value);
+
+ var mem2 = pool.Rent();
+
+ mem.Dispose();
+ mem2.Dispose();
+
+ Assert.Equal(2 * PinnedBlockMemoryPool.BlockSize, currentMemoryMetric.GetMeasurementSnapshot().EvaluateAsCounter());
+
+ // Eviction after returning everything to reset internal counters
+ pool.PerformEviction();
+
+ // Trigger eviction
+ pool.PerformEviction();
+
+ // Verify eviction updates current memory metric
+ Assert.Equal(0, currentMemoryMetric.GetMeasurementSnapshot().EvaluateAsCounter());
+ }
+
+ [Fact]
+ public void TotalAllocatedMetricTracksAllocatedMemory()
+ {
+ var testMeterFactory = new TestMeterFactory();
+ using var totalMemoryMetric = new MetricCollector(testMeterFactory, "Microsoft.AspNetCore.MemoryPool", "aspnetcore.memorypool.total_allocated");
+
+ var pool = new PinnedBlockMemoryPool(testMeterFactory);
+
+ Assert.Empty(totalMemoryMetric.GetMeasurementSnapshot());
+
+ var mem1 = pool.Rent();
+ var mem2 = pool.Rent();
+
+ // Each Rent that allocates a new block should increment total memory by block size
+ Assert.Equal(2 * PinnedBlockMemoryPool.BlockSize, totalMemoryMetric.GetMeasurementSnapshot().EvaluateAsCounter());
+
+ mem1.Dispose();
+ mem2.Dispose();
+
+ // Disposing (returning) blocks does not affect total memory
+ Assert.Equal(2 * PinnedBlockMemoryPool.BlockSize, totalMemoryMetric.GetMeasurementSnapshot().EvaluateAsCounter());
+ }
+
+ [Fact]
+ public void TotalRentedMetricTracksRentOperations()
+ {
+ var testMeterFactory = new TestMeterFactory();
+ using var rentMetric = new MetricCollector(testMeterFactory, "Microsoft.AspNetCore.MemoryPool", "aspnetcore.memorypool.total_rented");
+
+ var pool = new PinnedBlockMemoryPool(testMeterFactory);
+
+ Assert.Empty(rentMetric.GetMeasurementSnapshot());
+
+ var mem1 = pool.Rent();
+ var mem2 = pool.Rent();
+
+ // Each Rent should record the size of the block rented
+ Assert.Collection(rentMetric.GetMeasurementSnapshot(),
+ m => Assert.Equal(PinnedBlockMemoryPool.BlockSize, m.Value),
+ m => Assert.Equal(PinnedBlockMemoryPool.BlockSize, m.Value));
+
+ mem1.Dispose();
+ mem2.Dispose();
+
+ // Disposing does not affect rent metric
+ Assert.Equal(2 * PinnedBlockMemoryPool.BlockSize, rentMetric.GetMeasurementSnapshot().EvaluateAsCounter());
+ }
+
+ [Fact]
+ public void EvictedMemoryMetricTracksEvictedMemory()
+ {
+ var testMeterFactory = new TestMeterFactory();
+ using var evictMetric = new MetricCollector(testMeterFactory, "Microsoft.AspNetCore.MemoryPool", "aspnetcore.memorypool.evicted_memory");
+
+ var pool = new PinnedBlockMemoryPool(testMeterFactory);
+
+ // Fill the pool with some blocks
+ var blocks = new List>();
+ for (int i = 0; i < 10; i++)
+ {
+ blocks.Add(pool.Rent());
+ }
+ foreach (var block in blocks)
+ {
+ block.Dispose();
+ }
+ blocks.Clear();
+
+ Assert.Empty(evictMetric.GetMeasurementSnapshot());
+
+ // Eviction after returning everything to reset internal counters
+ pool.PerformEviction();
+
+ // Trigger eviction
+ pool.PerformEviction();
+
+ // At least some blocks should be evicted, each eviction records block size
+ Assert.NotEmpty(evictMetric.GetMeasurementSnapshot());
+ foreach (var measurement in evictMetric.GetMeasurementSnapshot())
+ {
+ Assert.Equal(PinnedBlockMemoryPool.BlockSize, measurement.Value);
+ }
+ }
+
+ // Smoke test to ensure that metrics are aggregated across multiple pools if the same meter factory is used
+ [Fact]
+ public void MetricsAreAggregatedAcrossPoolsWithSameMeterFactory()
+ {
+ var testMeterFactory = new TestMeterFactory();
+ using var rentMetric = new MetricCollector(testMeterFactory, "Microsoft.AspNetCore.MemoryPool", "aspnetcore.memorypool.total_rented");
+
+ var pool1 = new PinnedBlockMemoryPool(testMeterFactory);
+ var pool2 = new PinnedBlockMemoryPool(testMeterFactory);
+
+ var mem1 = pool1.Rent();
+ var mem2 = pool2.Rent();
+
+ // Both pools should contribute to the same metric stream
+ Assert.Equal(2 * PinnedBlockMemoryPool.BlockSize, rentMetric.GetMeasurementSnapshot().EvaluateAsCounter());
+
+ mem1.Dispose();
+ mem2.Dispose();
+
+ // Renting and returning from both pools should not interfere with metric collection
+ var mem3 = pool1.Rent();
+ var mem4 = pool2.Rent();
+
+ Assert.Equal(4 * PinnedBlockMemoryPool.BlockSize, rentMetric.GetMeasurementSnapshot().EvaluateAsCounter());
+
+ mem3.Dispose();
+ mem4.Dispose();
+ }
}
diff --git a/src/Servers/Kestrel/Core/test/PipelineExtensionTests.cs b/src/Servers/Kestrel/Core/test/PipelineExtensionTests.cs
index e9dfeff6d9bb..361886b9f3be 100644
--- a/src/Servers/Kestrel/Core/test/PipelineExtensionTests.cs
+++ b/src/Servers/Kestrel/Core/test/PipelineExtensionTests.cs
@@ -16,7 +16,7 @@ public class PipelineExtensionTests : IDisposable
private const int _ulongMaxValueLength = 20;
private readonly Pipe _pipe;
- private readonly MemoryPool _memoryPool = PinnedBlockMemoryPoolFactory.Create();
+ private readonly MemoryPool _memoryPool = TestMemoryPoolFactory.Create();
public PipelineExtensionTests()
{
diff --git a/src/Servers/Kestrel/Core/test/StartLineTests.cs b/src/Servers/Kestrel/Core/test/StartLineTests.cs
index d80b9d009ad5..234939468177 100644
--- a/src/Servers/Kestrel/Core/test/StartLineTests.cs
+++ b/src/Servers/Kestrel/Core/test/StartLineTests.cs
@@ -515,7 +515,7 @@ public void AuthorityForms(string rawTarget, string path, string query)
public StartLineTests()
{
- MemoryPool = PinnedBlockMemoryPoolFactory.Create();
+ MemoryPool = TestMemoryPoolFactory.Create();
var options = new PipeOptions(MemoryPool, readerScheduler: PipeScheduler.Inline, writerScheduler: PipeScheduler.Inline, useSynchronizationContext: false);
var pair = DuplexPipe.CreateConnectionPair(options, options);
Transport = pair.Transport;
diff --git a/src/Servers/Kestrel/Core/test/TestHelpers/TestInput.cs b/src/Servers/Kestrel/Core/test/TestHelpers/TestInput.cs
index 914fa9bb5f58..88a5c8cfea69 100644
--- a/src/Servers/Kestrel/Core/test/TestHelpers/TestInput.cs
+++ b/src/Servers/Kestrel/Core/test/TestHelpers/TestInput.cs
@@ -24,7 +24,7 @@ class TestInput : IDisposable
public TestInput(KestrelTrace log = null, ITimeoutControl timeoutControl = null)
{
- _memoryPool = PinnedBlockMemoryPoolFactory.Create();
+ _memoryPool = TestMemoryPoolFactory.Create();
var options = new PipeOptions(pool: _memoryPool, readerScheduler: PipeScheduler.Inline, writerScheduler: PipeScheduler.Inline, useSynchronizationContext: false);
var pair = DuplexPipe.CreateConnectionPair(options, options);
Transport = pair.Transport;
diff --git a/src/Servers/Kestrel/Kestrel/src/WebHostBuilderKestrelExtensions.cs b/src/Servers/Kestrel/Kestrel/src/WebHostBuilderKestrelExtensions.cs
index 7186715a6c5f..0869ba8169b8 100644
--- a/src/Servers/Kestrel/Kestrel/src/WebHostBuilderKestrelExtensions.cs
+++ b/src/Servers/Kestrel/Kestrel/src/WebHostBuilderKestrelExtensions.cs
@@ -7,7 +7,6 @@
using Microsoft.AspNetCore.Server.Kestrel.Core;
using Microsoft.AspNetCore.Server.Kestrel.Core.Internal;
using Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Infrastructure;
-using Microsoft.AspNetCore.Server.Kestrel.Transport.Sockets;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.DependencyInjection.Extensions;
using Microsoft.Extensions.Options;
@@ -79,15 +78,17 @@ public static IWebHostBuilder UseKestrel(this IWebHostBuilder hostBuilder)
///
public static IWebHostBuilder UseKestrelCore(this IWebHostBuilder hostBuilder)
{
+ hostBuilder.UseSockets();
hostBuilder.ConfigureServices(services =>
{
- // Don't override an already-configured transport
- services.TryAddSingleton();
-
services.AddTransient, KestrelServerOptionsSetup>();
services.AddSingleton();
services.AddSingleton();
services.AddSingleton();
+
+ services.AddSingleton();
+ services.TryAddEnumerable(ServiceDescriptor.Singleton(sp => sp.GetRequiredService()));
+ services.AddSingleton>(sp => sp.GetRequiredService());
});
if (OperatingSystem.IsWindows())
diff --git a/src/Servers/Kestrel/Kestrel/test/Microsoft.AspNetCore.Server.Kestrel.Tests.csproj b/src/Servers/Kestrel/Kestrel/test/Microsoft.AspNetCore.Server.Kestrel.Tests.csproj
index ea39d1e46d6f..301d784d9e6d 100644
--- a/src/Servers/Kestrel/Kestrel/test/Microsoft.AspNetCore.Server.Kestrel.Tests.csproj
+++ b/src/Servers/Kestrel/Kestrel/test/Microsoft.AspNetCore.Server.Kestrel.Tests.csproj
@@ -18,6 +18,7 @@
+
diff --git a/src/Servers/Kestrel/Kestrel/test/WebHostBuilderKestrelExtensionsTests.cs b/src/Servers/Kestrel/Kestrel/test/WebHostBuilderKestrelExtensionsTests.cs
index b24da893ab53..26a33f387764 100644
--- a/src/Servers/Kestrel/Kestrel/test/WebHostBuilderKestrelExtensionsTests.cs
+++ b/src/Servers/Kestrel/Kestrel/test/WebHostBuilderKestrelExtensionsTests.cs
@@ -1,17 +1,22 @@
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.
-using System.Collections;
+using System.Diagnostics;
using System.IO.Pipelines;
+using System.Reflection;
using Microsoft.AspNetCore.Connections;
using Microsoft.AspNetCore.Hosting;
using Microsoft.AspNetCore.Hosting.Server;
+using Microsoft.AspNetCore.InternalTesting;
using Microsoft.AspNetCore.Server.Kestrel.Core;
+using Microsoft.AspNetCore.Server.Kestrel.Core.Internal;
+using Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http;
using Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Infrastructure;
+using Microsoft.AspNetCore.Server.Kestrel.Transport.NamedPipes;
using Microsoft.AspNetCore.Server.Kestrel.Transport.NamedPipes.Internal;
using Microsoft.AspNetCore.Server.Kestrel.Transport.Sockets;
using Microsoft.Extensions.DependencyInjection;
-using Xunit;
+using Microsoft.Extensions.Options;
namespace Microsoft.AspNetCore.Server.Kestrel.Tests;
@@ -115,5 +120,112 @@ public void ServerIsKestrelServerImpl()
Assert.IsType(server.ServiceContext.Metrics);
Assert.Equal(PipeScheduler.ThreadPool, server.ServiceContext.Scheduler);
Assert.Equal(TimeProvider.System, server.ServiceContext.TimeProvider);
+
+ var handlers = (IHeartbeatHandler[])typeof(Heartbeat).GetField("_callbacks", BindingFlags.NonPublic | BindingFlags.Instance).GetValue(server.ServiceContext.Heartbeat);
+ Assert.Collection(handlers,
+ handler =>
+ {
+ Assert.Equal(typeof(DateHeaderValueManager), handler.GetType());
+ },
+ handler =>
+ {
+ Assert.Equal(typeof(ConnectionManager), handler.GetType());
+ },
+ handler =>
+ {
+ Assert.Equal(typeof(PinnedBlockMemoryPoolFactory), handler.GetType());
+ });
+ }
+
+ [Fact]
+ public void MemoryPoolFactorySetCorrectlyWithSockets()
+ {
+ var hostBuilder = new WebHostBuilder()
+ .UseSockets()
+ .UseKestrel()
+ .Configure(app => { });
+
+ var host = hostBuilder.Build();
+
+ var memoryPoolFactory = Assert.IsType(host.Services.GetRequiredService>());
+ Assert.Null(host.Services.GetService>());
+
+ Assert.Same(memoryPoolFactory, host.Services.GetRequiredService>().Value.MemoryPoolFactory);
+
+ // Swap order of UseKestrel and UseSockets
+ hostBuilder = new WebHostBuilder()
+ .UseKestrel()
+ .UseSockets()
+ .Configure(app => { });
+
+ host = hostBuilder.Build();
+
+ memoryPoolFactory = Assert.IsType(host.Services.GetRequiredService>());
+ Assert.Null(host.Services.GetService>());
+
+ Assert.Same(memoryPoolFactory, host.Services.GetRequiredService>().Value.MemoryPoolFactory);
+ }
+
+ [Fact]
+ public void SocketsHasDefaultMemoryPool()
+ {
+ var hostBuilder = new WebHostBuilder()
+ .UseSockets()
+ .Configure(app => { });
+
+ var host = hostBuilder.Build();
+
+ var memoryPoolFactory = host.Services.GetRequiredService>();
+ Assert.IsNotType(memoryPoolFactory);
+ Assert.Null(host.Services.GetService>());
+
+ Assert.Same(memoryPoolFactory, host.Services.GetRequiredService>().Value.MemoryPoolFactory);
+ }
+
+ [ConditionalFact]
+ [NamedPipesSupported]
+ public void MemoryPoolFactorySetCorrectlyWithNamedPipes()
+ {
+ var hostBuilder = new WebHostBuilder()
+ .UseNamedPipes()
+ .UseKestrel()
+ .Configure(app => { });
+
+ var host = hostBuilder.Build();
+
+ var memoryPoolFactory = Assert.IsType(host.Services.GetRequiredService>());
+ Assert.Null(host.Services.GetService>());
+
+ Assert.Same(memoryPoolFactory, host.Services.GetRequiredService>().Value.MemoryPoolFactory);
+
+ // Swap order of UseKestrel and UseNamedPipes
+ hostBuilder = new WebHostBuilder()
+ .UseKestrel()
+ .UseNamedPipes()
+ .Configure(app => { });
+
+ host = hostBuilder.Build();
+
+ memoryPoolFactory = Assert.IsType(host.Services.GetRequiredService>());
+ Assert.Null(host.Services.GetService>());
+
+ Assert.Same(memoryPoolFactory, host.Services.GetRequiredService>().Value.MemoryPoolFactory);
+ }
+
+ [ConditionalFact]
+ [NamedPipesSupported]
+ public void NamedPipesHasDefaultMemoryPool()
+ {
+ var hostBuilder = new WebHostBuilder()
+ .UseNamedPipes()
+ .Configure(app => { });
+
+ var host = hostBuilder.Build();
+
+ var memoryPoolFactory = host.Services.GetRequiredService>();
+ Assert.IsNotType(memoryPoolFactory);
+ Assert.Null(host.Services.GetService>());
+
+ Assert.Same(memoryPoolFactory, host.Services.GetRequiredService>().Value.MemoryPoolFactory);
}
}
diff --git a/src/Servers/Kestrel/Transport.NamedPipes/src/Internal/NamedPipeConnectionListener.cs b/src/Servers/Kestrel/Transport.NamedPipes/src/Internal/NamedPipeConnectionListener.cs
index ab172bc28476..39465a4f3219 100644
--- a/src/Servers/Kestrel/Transport.NamedPipes/src/Internal/NamedPipeConnectionListener.cs
+++ b/src/Servers/Kestrel/Transport.NamedPipes/src/Internal/NamedPipeConnectionListener.cs
@@ -40,7 +40,7 @@ public NamedPipeConnectionListener(
_log = loggerFactory.CreateLogger("Microsoft.AspNetCore.Server.Kestrel.Transport.NamedPipes");
_endpoint = endpoint;
_options = options;
- _memoryPool = options.MemoryPoolFactory();
+ _memoryPool = options.MemoryPoolFactory.Create();
_listeningToken = _listeningTokenSource.Token;
// Have to create the pool here (instead of DI) because the pool is specific to an endpoint.
_poolPolicy = new NamedPipeServerStreamPoolPolicy(endpoint, options);
diff --git a/src/Servers/Kestrel/Transport.NamedPipes/src/Microsoft.AspNetCore.Server.Kestrel.Transport.NamedPipes.csproj b/src/Servers/Kestrel/Transport.NamedPipes/src/Microsoft.AspNetCore.Server.Kestrel.Transport.NamedPipes.csproj
index e7b0816ec136..61735851bb84 100644
--- a/src/Servers/Kestrel/Transport.NamedPipes/src/Microsoft.AspNetCore.Server.Kestrel.Transport.NamedPipes.csproj
+++ b/src/Servers/Kestrel/Transport.NamedPipes/src/Microsoft.AspNetCore.Server.Kestrel.Transport.NamedPipes.csproj
@@ -21,6 +21,7 @@
+
diff --git a/src/Servers/Kestrel/Transport.NamedPipes/src/NamedPipeTransportOptions.cs b/src/Servers/Kestrel/Transport.NamedPipes/src/NamedPipeTransportOptions.cs
index a1d7d47f4854..133dc1ca6f8b 100644
--- a/src/Servers/Kestrel/Transport.NamedPipes/src/NamedPipeTransportOptions.cs
+++ b/src/Servers/Kestrel/Transport.NamedPipes/src/NamedPipeTransportOptions.cs
@@ -1,8 +1,9 @@
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.
-using System.Buffers;
using System.IO.Pipes;
+using Microsoft.AspNetCore.Connections;
+using Microsoft.AspNetCore.Server.Kestrel.Internal;
namespace Microsoft.AspNetCore.Server.Kestrel.Transport.NamedPipes;
@@ -116,5 +117,5 @@ public static NamedPipeServerStream CreateDefaultNamedPipeServerStream(CreateNam
}
}
- internal Func> MemoryPoolFactory { get; set; } = PinnedBlockMemoryPoolFactory.Create;
+ internal IMemoryPoolFactory MemoryPoolFactory { get; set; } = DefaultSimpleMemoryPoolFactory.Instance;
}
diff --git a/src/Servers/Kestrel/Transport.NamedPipes/src/WebHostBuilderNamedPipeExtensions.cs b/src/Servers/Kestrel/Transport.NamedPipes/src/WebHostBuilderNamedPipeExtensions.cs
index 53f94f801e74..3b64cc2c4f7e 100644
--- a/src/Servers/Kestrel/Transport.NamedPipes/src/WebHostBuilderNamedPipeExtensions.cs
+++ b/src/Servers/Kestrel/Transport.NamedPipes/src/WebHostBuilderNamedPipeExtensions.cs
@@ -33,7 +33,15 @@ public static IWebHostBuilder UseNamedPipes(this IWebHostBuilder hostBuilder)
{
services.TryAddSingleton();
services.AddSingleton();
+
+ services.TryAddSingleton, DefaultMemoryPoolFactory>();
+ services.AddOptions().Configure((NamedPipeTransportOptions options, IMemoryPoolFactory factory) =>
+ {
+ // Set the IMemoryPoolFactory from DI on NamedPipeTransportOptions. Usually this should be the PinnedBlockMemoryPoolFactory from UseKestrelCore.
+ options.MemoryPoolFactory = factory;
+ });
});
+
return hostBuilder;
}
diff --git a/src/Servers/Kestrel/Transport.Sockets/src/Client/SocketConnectionFactory.cs b/src/Servers/Kestrel/Transport.Sockets/src/Client/SocketConnectionFactory.cs
index db77bbe811d3..ef6f2c771579 100644
--- a/src/Servers/Kestrel/Transport.Sockets/src/Client/SocketConnectionFactory.cs
+++ b/src/Servers/Kestrel/Transport.Sockets/src/Client/SocketConnectionFactory.cs
@@ -27,7 +27,7 @@ public SocketConnectionFactory(IOptions options, ILogger
ArgumentNullException.ThrowIfNull(loggerFactory);
_options = options.Value;
- _memoryPool = options.Value.MemoryPoolFactory();
+ _memoryPool = options.Value.MemoryPoolFactory.Create();
_trace = loggerFactory.CreateLogger("Microsoft.AspNetCore.Server.Kestrel.Transport.Sockets.Client");
var maxReadBufferSize = _options.MaxReadBufferSize ?? 0;
diff --git a/src/Servers/Kestrel/Transport.Sockets/src/Internal/SocketConnection.cs b/src/Servers/Kestrel/Transport.Sockets/src/Internal/SocketConnection.cs
index e93c929f0a5b..e8655b08d0ff 100644
--- a/src/Servers/Kestrel/Transport.Sockets/src/Internal/SocketConnection.cs
+++ b/src/Servers/Kestrel/Transport.Sockets/src/Internal/SocketConnection.cs
@@ -12,7 +12,8 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Transport.Sockets.Internal;
internal sealed partial class SocketConnection : TransportConnection
{
- private static readonly int MinAllocBufferSize = PinnedBlockMemoryPool.BlockSize / 2;
+ // PinnedBlockMemoryPool.BlockSize / 2
+ private const int MinAllocBufferSize = 4096 / 2;
private readonly Socket _socket;
private readonly ILogger _logger;
diff --git a/src/Servers/Kestrel/Transport.Sockets/src/Microsoft.AspNetCore.Server.Kestrel.Transport.Sockets.csproj b/src/Servers/Kestrel/Transport.Sockets/src/Microsoft.AspNetCore.Server.Kestrel.Transport.Sockets.csproj
index 055f5f8e297e..9f225bde1142 100644
--- a/src/Servers/Kestrel/Transport.Sockets/src/Microsoft.AspNetCore.Server.Kestrel.Transport.Sockets.csproj
+++ b/src/Servers/Kestrel/Transport.Sockets/src/Microsoft.AspNetCore.Server.Kestrel.Transport.Sockets.csproj
@@ -12,12 +12,12 @@
-
+
diff --git a/src/Servers/Kestrel/Transport.Sockets/src/SocketConnectionContextFactory.cs b/src/Servers/Kestrel/Transport.Sockets/src/SocketConnectionContextFactory.cs
index db98e36842e1..86d2ec2a2d22 100644
--- a/src/Servers/Kestrel/Transport.Sockets/src/SocketConnectionContextFactory.cs
+++ b/src/Servers/Kestrel/Transport.Sockets/src/SocketConnectionContextFactory.cs
@@ -47,7 +47,7 @@ public SocketConnectionContextFactory(SocketConnectionFactoryOptions options, IL
for (var i = 0; i < _settingsCount; i++)
{
- var memoryPool = _options.MemoryPoolFactory();
+ var memoryPool = _options.MemoryPoolFactory.Create();
var transportScheduler = options.UnsafePreferInlineScheduling ? PipeScheduler.Inline : new IOQueue();
_settings[i] = new QueueSettings()
@@ -62,7 +62,7 @@ public SocketConnectionContextFactory(SocketConnectionFactoryOptions options, IL
}
else
{
- var memoryPool = _options.MemoryPoolFactory();
+ var memoryPool = _options.MemoryPoolFactory.Create();
var transportScheduler = options.UnsafePreferInlineScheduling ? PipeScheduler.Inline : PipeScheduler.ThreadPool;
_settings = new QueueSettings[]
diff --git a/src/Servers/Kestrel/Transport.Sockets/src/SocketConnectionFactoryOptions.cs b/src/Servers/Kestrel/Transport.Sockets/src/SocketConnectionFactoryOptions.cs
index 403f6fc108de..e77950c87c8e 100644
--- a/src/Servers/Kestrel/Transport.Sockets/src/SocketConnectionFactoryOptions.cs
+++ b/src/Servers/Kestrel/Transport.Sockets/src/SocketConnectionFactoryOptions.cs
@@ -1,7 +1,8 @@
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.
-using System.Buffers;
+using Microsoft.AspNetCore.Connections;
+using Microsoft.AspNetCore.Server.Kestrel.Internal;
namespace Microsoft.AspNetCore.Server.Kestrel.Transport.Sockets;
@@ -67,5 +68,5 @@ internal SocketConnectionFactoryOptions(SocketTransportOptions transportOptions)
///
public bool UnsafePreferInlineScheduling { get; set; }
- internal Func> MemoryPoolFactory { get; set; } = PinnedBlockMemoryPoolFactory.Create;
+ internal IMemoryPoolFactory MemoryPoolFactory { get; set; } = DefaultSimpleMemoryPoolFactory.Instance;
}
diff --git a/src/Servers/Kestrel/Transport.Sockets/src/SocketTransportOptions.cs b/src/Servers/Kestrel/Transport.Sockets/src/SocketTransportOptions.cs
index ad1a877df63c..822e665b1795 100644
--- a/src/Servers/Kestrel/Transport.Sockets/src/SocketTransportOptions.cs
+++ b/src/Servers/Kestrel/Transport.Sockets/src/SocketTransportOptions.cs
@@ -1,10 +1,10 @@
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.
-using System.Buffers;
using System.Net;
using System.Net.Sockets;
using Microsoft.AspNetCore.Connections;
+using Microsoft.AspNetCore.Server.Kestrel.Internal;
namespace Microsoft.AspNetCore.Server.Kestrel.Transport.Sockets;
@@ -166,5 +166,5 @@ public static Socket CreateDefaultBoundListenSocket(EndPoint endpoint)
return listenSocket;
}
- internal Func> MemoryPoolFactory { get; set; } = System.Buffers.PinnedBlockMemoryPoolFactory.Create;
+ internal IMemoryPoolFactory MemoryPoolFactory { get; set; } = DefaultSimpleMemoryPoolFactory.Instance;
}
diff --git a/src/Servers/Kestrel/Transport.Sockets/src/WebHostBuilderSocketExtensions.cs b/src/Servers/Kestrel/Transport.Sockets/src/WebHostBuilderSocketExtensions.cs
index 046522e22048..c5d7c54b77f3 100644
--- a/src/Servers/Kestrel/Transport.Sockets/src/WebHostBuilderSocketExtensions.cs
+++ b/src/Servers/Kestrel/Transport.Sockets/src/WebHostBuilderSocketExtensions.cs
@@ -2,8 +2,10 @@
// The .NET Foundation licenses this file to you under the MIT license.
using Microsoft.AspNetCore.Connections;
+using Microsoft.AspNetCore.Server.Kestrel.Internal;
using Microsoft.AspNetCore.Server.Kestrel.Transport.Sockets;
using Microsoft.Extensions.DependencyInjection;
+using Microsoft.Extensions.DependencyInjection.Extensions;
namespace Microsoft.AspNetCore.Hosting;
@@ -25,7 +27,14 @@ public static IWebHostBuilder UseSockets(this IWebHostBuilder hostBuilder)
{
return hostBuilder.ConfigureServices(services =>
{
- services.AddSingleton();
+ services.TryAddSingleton();
+
+ services.TryAddSingleton, DefaultSimpleMemoryPoolFactory>();
+ services.AddOptions().Configure((SocketTransportOptions options, IMemoryPoolFactory factory) =>
+ {
+ // Set the IMemoryPoolFactory from DI on SocketTransportOptions. Usually this should be the PinnedBlockMemoryPoolFactory from UseKestrelCore.
+ options.MemoryPoolFactory = factory;
+ });
});
}
diff --git a/src/Servers/Kestrel/perf/Microbenchmarks/ChunkWriterBenchmark.cs b/src/Servers/Kestrel/perf/Microbenchmarks/ChunkWriterBenchmark.cs
index f4f5ca0348cf..adf66afad01a 100644
--- a/src/Servers/Kestrel/perf/Microbenchmarks/ChunkWriterBenchmark.cs
+++ b/src/Servers/Kestrel/perf/Microbenchmarks/ChunkWriterBenchmark.cs
@@ -20,7 +20,7 @@ public class ChunkWriterBenchmark
[GlobalSetup]
public void Setup()
{
- _memoryPool = PinnedBlockMemoryPoolFactory.Create();
+ _memoryPool = TestMemoryPoolFactory.Create();
var pipe = new Pipe(new PipeOptions(_memoryPool));
_reader = pipe.Reader;
_writer = pipe.Writer;
diff --git a/src/Servers/Kestrel/perf/Microbenchmarks/HeaderCollectionBenchmark.cs b/src/Servers/Kestrel/perf/Microbenchmarks/HeaderCollectionBenchmark.cs
index 2ba73acc204e..88448414f662 100644
--- a/src/Servers/Kestrel/perf/Microbenchmarks/HeaderCollectionBenchmark.cs
+++ b/src/Servers/Kestrel/perf/Microbenchmarks/HeaderCollectionBenchmark.cs
@@ -324,7 +324,7 @@ string ReadHeaders()
[IterationSetup]
public void Setup()
{
- var memoryPool = PinnedBlockMemoryPoolFactory.Create();
+ var memoryPool = TestMemoryPoolFactory.Create();
var options = new PipeOptions(memoryPool, readerScheduler: PipeScheduler.Inline, writerScheduler: PipeScheduler.Inline, useSynchronizationContext: false);
var pair = DuplexPipe.CreateConnectionPair(options, options);
diff --git a/src/Servers/Kestrel/perf/Microbenchmarks/Http1ConnectionBenchmark.cs b/src/Servers/Kestrel/perf/Microbenchmarks/Http1ConnectionBenchmark.cs
index 16697ca82441..4c73e1b10d5e 100644
--- a/src/Servers/Kestrel/perf/Microbenchmarks/Http1ConnectionBenchmark.cs
+++ b/src/Servers/Kestrel/perf/Microbenchmarks/Http1ConnectionBenchmark.cs
@@ -27,7 +27,7 @@ public class Http1ConnectionBenchmark
[GlobalSetup]
public void Setup()
{
- var memoryPool = PinnedBlockMemoryPoolFactory.Create();
+ var memoryPool = TestMemoryPoolFactory.Create();
var options = new PipeOptions(memoryPool, readerScheduler: PipeScheduler.Inline, writerScheduler: PipeScheduler.Inline, useSynchronizationContext: false);
var pair = DuplexPipe.CreateConnectionPair(options, options);
diff --git a/src/Servers/Kestrel/perf/Microbenchmarks/Http1ConnectionParsingOverheadBenchmark.cs b/src/Servers/Kestrel/perf/Microbenchmarks/Http1ConnectionParsingOverheadBenchmark.cs
index 356953b8cafa..a4cc32b06b6b 100644
--- a/src/Servers/Kestrel/perf/Microbenchmarks/Http1ConnectionParsingOverheadBenchmark.cs
+++ b/src/Servers/Kestrel/perf/Microbenchmarks/Http1ConnectionParsingOverheadBenchmark.cs
@@ -23,7 +23,7 @@ public class Http1ConnectionParsingOverheadBenchmark
[IterationSetup]
public void Setup()
{
- var memoryPool = PinnedBlockMemoryPoolFactory.Create();
+ var memoryPool = TestMemoryPoolFactory.Create();
var options = new PipeOptions(memoryPool, readerScheduler: PipeScheduler.Inline, writerScheduler: PipeScheduler.Inline, useSynchronizationContext: false);
var pair = DuplexPipe.CreateConnectionPair(options, options);
diff --git a/src/Servers/Kestrel/perf/Microbenchmarks/Http1LargeWritingBenchmark.cs b/src/Servers/Kestrel/perf/Microbenchmarks/Http1LargeWritingBenchmark.cs
index fa8d0dddf4e8..35c7b5ad9388 100644
--- a/src/Servers/Kestrel/perf/Microbenchmarks/Http1LargeWritingBenchmark.cs
+++ b/src/Servers/Kestrel/perf/Microbenchmarks/Http1LargeWritingBenchmark.cs
@@ -28,7 +28,7 @@ public class Http1LargeWritingBenchmark
[GlobalSetup]
public void GlobalSetup()
{
- _memoryPool = PinnedBlockMemoryPoolFactory.Create();
+ _memoryPool = TestMemoryPoolFactory.Create();
_http1Connection = MakeHttp1Connection();
_consumeResponseBodyTask = ConsumeResponseBody();
}
diff --git a/src/Servers/Kestrel/perf/Microbenchmarks/Http1ReadingBenchmark.cs b/src/Servers/Kestrel/perf/Microbenchmarks/Http1ReadingBenchmark.cs
index a5c18cbadf4d..8bacc955ad04 100644
--- a/src/Servers/Kestrel/perf/Microbenchmarks/Http1ReadingBenchmark.cs
+++ b/src/Servers/Kestrel/perf/Microbenchmarks/Http1ReadingBenchmark.cs
@@ -35,7 +35,7 @@ public class Http1ReadingBenchmark
[GlobalSetup]
public void GlobalSetup()
{
- _memoryPool = PinnedBlockMemoryPoolFactory.Create();
+ _memoryPool = TestMemoryPoolFactory.Create();
_http1Connection = MakeHttp1Connection();
}
diff --git a/src/Servers/Kestrel/perf/Microbenchmarks/Http1WritingBenchmark.cs b/src/Servers/Kestrel/perf/Microbenchmarks/Http1WritingBenchmark.cs
index 31db6447384b..6d6d1de8a350 100644
--- a/src/Servers/Kestrel/perf/Microbenchmarks/Http1WritingBenchmark.cs
+++ b/src/Servers/Kestrel/perf/Microbenchmarks/Http1WritingBenchmark.cs
@@ -35,7 +35,7 @@ public class Http1WritingBenchmark
[GlobalSetup]
public void GlobalSetup()
{
- _memoryPool = PinnedBlockMemoryPoolFactory.Create();
+ _memoryPool = TestMemoryPoolFactory.Create();
_http1Connection = MakeHttp1Connection();
_consumeResponseBodyTask = ConsumeResponseBody();
}
diff --git a/src/Servers/Kestrel/perf/Microbenchmarks/Http2/Http2ConnectionBenchmarkBase.cs b/src/Servers/Kestrel/perf/Microbenchmarks/Http2/Http2ConnectionBenchmarkBase.cs
index 8c4f22ddfc42..bf90e4859427 100644
--- a/src/Servers/Kestrel/perf/Microbenchmarks/Http2/Http2ConnectionBenchmarkBase.cs
+++ b/src/Servers/Kestrel/perf/Microbenchmarks/Http2/Http2ConnectionBenchmarkBase.cs
@@ -43,7 +43,7 @@ public abstract class Http2ConnectionBenchmarkBase
public virtual void GlobalSetup()
{
- _memoryPool = PinnedBlockMemoryPoolFactory.Create();
+ _memoryPool = TestMemoryPoolFactory.Create();
var options = new PipeOptions(_memoryPool, readerScheduler: PipeScheduler.Inline, writerScheduler: PipeScheduler.Inline, useSynchronizationContext: false);
diff --git a/src/Servers/Kestrel/perf/Microbenchmarks/Http2/Http2FrameWriterBenchmark.cs b/src/Servers/Kestrel/perf/Microbenchmarks/Http2/Http2FrameWriterBenchmark.cs
index 03589ed07b77..138e82a97c85 100644
--- a/src/Servers/Kestrel/perf/Microbenchmarks/Http2/Http2FrameWriterBenchmark.cs
+++ b/src/Servers/Kestrel/perf/Microbenchmarks/Http2/Http2FrameWriterBenchmark.cs
@@ -24,7 +24,7 @@ public class Http2FrameWriterBenchmark
[GlobalSetup]
public void GlobalSetup()
{
- _memoryPool = PinnedBlockMemoryPoolFactory.Create();
+ _memoryPool = TestMemoryPoolFactory.Create();
var options = new PipeOptions(_memoryPool, readerScheduler: PipeScheduler.Inline, writerScheduler: PipeScheduler.Inline, useSynchronizationContext: false);
_pipe = new Pipe(options);
diff --git a/src/Servers/Kestrel/perf/Microbenchmarks/HttpProtocolFeatureCollection.cs b/src/Servers/Kestrel/perf/Microbenchmarks/HttpProtocolFeatureCollection.cs
index bb513fd002af..297325a1e15b 100644
--- a/src/Servers/Kestrel/perf/Microbenchmarks/HttpProtocolFeatureCollection.cs
+++ b/src/Servers/Kestrel/perf/Microbenchmarks/HttpProtocolFeatureCollection.cs
@@ -226,7 +226,7 @@ public IHttpNotFoundFeature Get_IHttpNotFoundFeature()
public HttpProtocolFeatureCollection()
{
- var memoryPool = PinnedBlockMemoryPoolFactory.Create();
+ var memoryPool = TestMemoryPoolFactory.Create();
var options = new PipeOptions(memoryPool, readerScheduler: PipeScheduler.Inline, writerScheduler: PipeScheduler.Inline, useSynchronizationContext: false);
var pair = DuplexPipe.CreateConnectionPair(options, options);
diff --git a/src/Servers/Kestrel/perf/Microbenchmarks/Microsoft.AspNetCore.Server.Kestrel.Microbenchmarks.csproj b/src/Servers/Kestrel/perf/Microbenchmarks/Microsoft.AspNetCore.Server.Kestrel.Microbenchmarks.csproj
index 528290dd4396..0d4fff0fdc0f 100644
--- a/src/Servers/Kestrel/perf/Microbenchmarks/Microsoft.AspNetCore.Server.Kestrel.Microbenchmarks.csproj
+++ b/src/Servers/Kestrel/perf/Microbenchmarks/Microsoft.AspNetCore.Server.Kestrel.Microbenchmarks.csproj
@@ -18,7 +18,6 @@
-
diff --git a/src/Servers/Kestrel/perf/Microbenchmarks/PipeThroughputBenchmark.cs b/src/Servers/Kestrel/perf/Microbenchmarks/PipeThroughputBenchmark.cs
index 9a941cab1b63..3550af9079f0 100644
--- a/src/Servers/Kestrel/perf/Microbenchmarks/PipeThroughputBenchmark.cs
+++ b/src/Servers/Kestrel/perf/Microbenchmarks/PipeThroughputBenchmark.cs
@@ -19,7 +19,7 @@ public class PipeThroughputBenchmark
[IterationSetup]
public void Setup()
{
- _memoryPool = PinnedBlockMemoryPoolFactory.Create();
+ _memoryPool = TestMemoryPoolFactory.Create();
_pipe = new Pipe(new PipeOptions(_memoryPool));
}
diff --git a/src/Servers/Kestrel/perf/Microbenchmarks/RequestParsingBenchmark.cs b/src/Servers/Kestrel/perf/Microbenchmarks/RequestParsingBenchmark.cs
index 819ca5441d3b..0a0cb4b9002a 100644
--- a/src/Servers/Kestrel/perf/Microbenchmarks/RequestParsingBenchmark.cs
+++ b/src/Servers/Kestrel/perf/Microbenchmarks/RequestParsingBenchmark.cs
@@ -24,7 +24,7 @@ public class RequestParsingBenchmark
[IterationSetup]
public void Setup()
{
- _memoryPool = PinnedBlockMemoryPoolFactory.Create();
+ _memoryPool = TestMemoryPoolFactory.Create();
var options = new PipeOptions(_memoryPool, readerScheduler: PipeScheduler.Inline, writerScheduler: PipeScheduler.Inline, useSynchronizationContext: false);
var pair = DuplexPipe.CreateConnectionPair(options, options);
diff --git a/src/Servers/Kestrel/perf/Microbenchmarks/ResponseHeaderCollectionBenchmark.cs b/src/Servers/Kestrel/perf/Microbenchmarks/ResponseHeaderCollectionBenchmark.cs
index 983f39f28eed..359931b0f371 100644
--- a/src/Servers/Kestrel/perf/Microbenchmarks/ResponseHeaderCollectionBenchmark.cs
+++ b/src/Servers/Kestrel/perf/Microbenchmarks/ResponseHeaderCollectionBenchmark.cs
@@ -172,7 +172,7 @@ private void Unknown(int count)
[IterationSetup]
public void Setup()
{
- var memoryPool = PinnedBlockMemoryPoolFactory.Create();
+ var memoryPool = TestMemoryPoolFactory.Create();
var options = new PipeOptions(memoryPool, readerScheduler: PipeScheduler.Inline, writerScheduler: PipeScheduler.Inline, useSynchronizationContext: false);
var pair = DuplexPipe.CreateConnectionPair(options, options);
diff --git a/src/Servers/Kestrel/shared/DefaultSimpleMemoryPoolFactory.cs b/src/Servers/Kestrel/shared/DefaultSimpleMemoryPoolFactory.cs
new file mode 100644
index 000000000000..d3c4f94c333c
--- /dev/null
+++ b/src/Servers/Kestrel/shared/DefaultSimpleMemoryPoolFactory.cs
@@ -0,0 +1,17 @@
+// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+
+using System.Buffers;
+using Microsoft.AspNetCore.Connections;
+
+namespace Microsoft.AspNetCore.Server.Kestrel.Internal;
+
+internal sealed class DefaultSimpleMemoryPoolFactory : IMemoryPoolFactory
+{
+ public static DefaultSimpleMemoryPoolFactory Instance { get; } = new DefaultSimpleMemoryPoolFactory();
+
+ public MemoryPool Create()
+ {
+ return MemoryPool.Shared;
+ }
+}
diff --git a/src/Servers/Kestrel/shared/test/Http3/Http3InMemory.cs b/src/Servers/Kestrel/shared/test/Http3/Http3InMemory.cs
index 64ebcdb07b41..55321b0c33b4 100644
--- a/src/Servers/Kestrel/shared/test/Http3/Http3InMemory.cs
+++ b/src/Servers/Kestrel/shared/test/Http3/Http3InMemory.cs
@@ -1,7 +1,6 @@
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.
-using System;
using System.Buffers;
using System.Collections.Concurrent;
using System.Diagnostics;
@@ -74,7 +73,7 @@ public void OnTimeout(TimeoutReason reason)
private FakeTimeProvider _fakeTimeProvider;
internal HttpConnection _httpConnection;
internal readonly TimeoutControl _timeoutControl;
- internal readonly MemoryPool _memoryPool = PinnedBlockMemoryPoolFactory.Create();
+ internal readonly MemoryPool _memoryPool = TestMemoryPoolFactory.Create();
internal readonly ConcurrentQueue _streamContextPool = new ConcurrentQueue();
protected Task _connectionTask;
internal ILogger Logger { get; }
diff --git a/src/Servers/Kestrel/shared/test/TestContextFactory.cs b/src/Servers/Kestrel/shared/test/TestContextFactory.cs
index 3baf69b6348a..c019bc0939fb 100644
--- a/src/Servers/Kestrel/shared/test/TestContextFactory.cs
+++ b/src/Servers/Kestrel/shared/test/TestContextFactory.cs
@@ -93,7 +93,7 @@ public static HttpMultiplexedConnectionContext CreateHttp3ConnectionContext(
connectionContext,
serviceContext ?? CreateServiceContext(new KestrelServerOptions()),
connectionFeatures ?? new FeatureCollection(),
- memoryPool ?? PinnedBlockMemoryPoolFactory.Create(),
+ memoryPool ?? TestMemoryPoolFactory.Create(),
localEndPoint,
remoteEndPoint,
metricsContext)
diff --git a/src/Servers/Kestrel/shared/test/TestServiceContext.cs b/src/Servers/Kestrel/shared/test/TestServiceContext.cs
index 85692f1fe341..2987d52f5461 100644
--- a/src/Servers/Kestrel/shared/test/TestServiceContext.cs
+++ b/src/Servers/Kestrel/shared/test/TestServiceContext.cs
@@ -3,6 +3,7 @@
using System.Buffers;
using System.IO.Pipelines;
+using Microsoft.AspNetCore.Connections;
using Microsoft.AspNetCore.Server.Kestrel.Core;
using Microsoft.AspNetCore.Server.Kestrel.Core.Internal;
using Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http;
@@ -73,7 +74,19 @@ private void Initialize(ILoggerFactory loggerFactory, KestrelTrace kestrelTrace,
public FakeTimeProvider FakeTimeProvider { get; set; }
- public Func> MemoryPoolFactory { get; set; } = System.Buffers.PinnedBlockMemoryPoolFactory.Create;
+ public IMemoryPoolFactory MemoryPoolFactory { get; set; } = new WrappingMemoryPoolFactory(() => TestMemoryPoolFactory.Create());
public string DateHeaderValue => DateHeaderValueManager.GetDateHeaderValues().String;
+
+ internal sealed class WrappingMemoryPoolFactory : IMemoryPoolFactory
+ {
+ private readonly Func> _memoryPoolFactory;
+
+ public WrappingMemoryPoolFactory(Func> memoryPoolFactory)
+ {
+ _memoryPoolFactory = memoryPoolFactory;
+ }
+
+ public MemoryPool Create() => _memoryPoolFactory();
+ }
}
diff --git a/src/Servers/Kestrel/shared/test/TransportTestHelpers/DiagnosticMemoryPoolFactory.cs b/src/Servers/Kestrel/shared/test/TransportTestHelpers/DiagnosticMemoryPoolFactory.cs
index d27bf8e11635..91a266249cd1 100644
--- a/src/Servers/Kestrel/shared/test/TransportTestHelpers/DiagnosticMemoryPoolFactory.cs
+++ b/src/Servers/Kestrel/shared/test/TransportTestHelpers/DiagnosticMemoryPoolFactory.cs
@@ -6,10 +6,11 @@
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
+using Microsoft.AspNetCore.Connections;
namespace Microsoft.AspNetCore.Server.Kestrel.FunctionalTests;
-public class DiagnosticMemoryPoolFactory
+public class DiagnosticMemoryPoolFactory : IMemoryPoolFactory
{
private readonly bool _allowLateReturn;
diff --git a/src/Servers/Kestrel/shared/test/TransportTestHelpers/TestServer.cs b/src/Servers/Kestrel/shared/test/TransportTestHelpers/TestServer.cs
index 4c1813ed371a..88ad8f350c17 100644
--- a/src/Servers/Kestrel/shared/test/TransportTestHelpers/TestServer.cs
+++ b/src/Servers/Kestrel/shared/test/TransportTestHelpers/TestServer.cs
@@ -1,26 +1,19 @@
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.
-using System;
-using System.Collections.Generic;
using System.Globalization;
-using System.Linq;
using System.Net;
-using System.Reflection;
-using System.Threading;
-using System.Threading.Tasks;
using Microsoft.AspNetCore.Builder;
using Microsoft.AspNetCore.Connections;
using Microsoft.AspNetCore.Hosting;
using Microsoft.AspNetCore.Hosting.Server;
using Microsoft.AspNetCore.Http;
-using Microsoft.AspNetCore.Server.Kestrel.Core;
using Microsoft.AspNetCore.InternalTesting;
+using Microsoft.AspNetCore.Server.Kestrel.Core;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Hosting;
using Microsoft.Extensions.Logging;
using Microsoft.Extensions.Options;
-using Xunit;
namespace Microsoft.AspNetCore.Server.Kestrel.FunctionalTests;
@@ -74,7 +67,7 @@ public TestServer(RequestDelegate app, TestServiceContext context, Action
{
webHostBuilder
@@ -85,6 +78,10 @@ public TestServer(RequestDelegate app, TestServiceContext context, Action
{
+ if (context.MemoryPoolFactory != null)
+ {
+ services.AddSingleton>(context.MemoryPoolFactory);
+ }
services.AddSingleton(this);
services.AddSingleton(context.LoggerFactory);
services.AddSingleton();
diff --git a/src/Servers/Kestrel/test/FunctionalTests/Http2/ShutdownTests.cs b/src/Servers/Kestrel/test/FunctionalTests/Http2/ShutdownTests.cs
index 7b2469a1b217..7d9ba4bd8f70 100644
--- a/src/Servers/Kestrel/test/FunctionalTests/Http2/ShutdownTests.cs
+++ b/src/Servers/Kestrel/test/FunctionalTests/Http2/ShutdownTests.cs
@@ -168,7 +168,7 @@ public async Task GracefulTurnsAbortiveIfRequestsDoNotFinish()
var testContext = new TestServiceContext(LoggerFactory)
{
- MemoryPoolFactory = () => new PinnedBlockMemoryPool()
+ MemoryPoolFactory = new TestServiceContext.WrappingMemoryPoolFactory(() => TestMemoryPoolFactory.CreatePinnedBlockMemoryPool()),
};
ThrowOnUngracefulShutdown = false;
diff --git a/src/Servers/Kestrel/test/FunctionalTests/MaxRequestBufferSizeTests.cs b/src/Servers/Kestrel/test/FunctionalTests/MaxRequestBufferSizeTests.cs
index 309c89be5a48..3ddaa21972e5 100644
--- a/src/Servers/Kestrel/test/FunctionalTests/MaxRequestBufferSizeTests.cs
+++ b/src/Servers/Kestrel/test/FunctionalTests/MaxRequestBufferSizeTests.cs
@@ -1,23 +1,17 @@
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.
-using System;
-using System.Buffers;
-using System.Collections.Generic;
-using System.IO;
-using System.Linq;
using System.Net;
using System.Net.Sockets;
using System.Text;
-using System.Threading.Tasks;
using Microsoft.AspNetCore.Builder;
+using Microsoft.AspNetCore.Connections;
using Microsoft.AspNetCore.Hosting;
using Microsoft.AspNetCore.Http;
-using Microsoft.AspNetCore.Server.Kestrel.FunctionalTests;
using Microsoft.AspNetCore.InternalTesting;
+using Microsoft.AspNetCore.Server.Kestrel.FunctionalTests;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Hosting;
-using Xunit;
#if SOCKETS
namespace Microsoft.AspNetCore.Server.Kestrel.Sockets.FunctionalTests;
@@ -132,7 +126,7 @@ public async Task LargeUpload(long? maxRequestBufferSize, bool connectionAdapter
var memoryPoolFactory = new DiagnosticMemoryPoolFactory(allowLateReturn: true);
- using (var host = await StartHost(maxRequestBufferSize, data, connectionAdapter, startReadingRequestBody, clientFinishedSendingRequestBody, memoryPoolFactory.Create))
+ using (var host = await StartHost(maxRequestBufferSize, data, connectionAdapter, startReadingRequestBody, clientFinishedSendingRequestBody, memoryPoolFactory))
{
var port = host.GetPort();
using (var socket = CreateSocket(port))
@@ -225,7 +219,7 @@ public async Task ServerShutsDownGracefullyWhenMaxRequestBufferSizeExceeded()
var memoryPoolFactory = new DiagnosticMemoryPoolFactory(allowLateReturn: true);
- using (var host = await StartHost(16 * 1024, data, false, startReadingRequestBody, clientFinishedSendingRequestBody, memoryPoolFactory.Create))
+ using (var host = await StartHost(16 * 1024, data, false, startReadingRequestBody, clientFinishedSendingRequestBody, memoryPoolFactory))
{
var port = host.GetPort();
using (var socket = CreateSocket(port))
@@ -306,9 +300,9 @@ private async Task StartHost(long? maxRequestBufferSize,
bool useConnectionAdapter,
TaskCompletionSource startReadingRequestBody,
TaskCompletionSource clientFinishedSendingRequestBody,
- Func> memoryPoolFactory = null)
+ IMemoryPoolFactory memoryPoolFactory = null)
{
- var host = TransportSelector.GetHostBuilder(memoryPoolFactory, maxRequestBufferSize)
+ var host = TransportSelector.GetHostBuilder(maxRequestBufferSize)
.ConfigureWebHost(webHostBuilder =>
{
webHostBuilder
@@ -341,6 +335,13 @@ private async Task StartHost(long? maxRequestBufferSize,
options.Limits.MaxRequestBodySize = _dataLength;
})
.UseContentRoot(Directory.GetCurrentDirectory())
+ .ConfigureServices(services =>
+ {
+ if (memoryPoolFactory != null)
+ {
+ services.AddSingleton>(memoryPoolFactory);
+ }
+ })
.Configure(app => app.Run(async context =>
{
await startReadingRequestBody.Task.TimeoutAfter(TimeSpan.FromSeconds(120));
diff --git a/src/Servers/Kestrel/test/InMemory.FunctionalTests/Http2/Http2TestBase.cs b/src/Servers/Kestrel/test/InMemory.FunctionalTests/Http2/Http2TestBase.cs
index e4d452b91aa0..6fdfc0153206 100644
--- a/src/Servers/Kestrel/test/InMemory.FunctionalTests/Http2/Http2TestBase.cs
+++ b/src/Servers/Kestrel/test/InMemory.FunctionalTests/Http2/Http2TestBase.cs
@@ -112,7 +112,7 @@ protected static IEnumerable> ReadRateRequestHeader
protected static readonly byte[] _noData = new byte[0];
protected static readonly byte[] _maxData = Encoding.ASCII.GetBytes(new string('a', Http2PeerSettings.MinAllowedMaxFrameSize));
- private readonly MemoryPool _memoryPool = PinnedBlockMemoryPoolFactory.Create();
+ private readonly MemoryPool _memoryPool = TestMemoryPoolFactory.Create();
internal readonly Http2PeerSettings _clientSettings = new Http2PeerSettings();
internal readonly HPackDecoder _hpackDecoder;
diff --git a/src/Servers/Kestrel/test/InMemory.FunctionalTests/InMemory.FunctionalTests.csproj b/src/Servers/Kestrel/test/InMemory.FunctionalTests/InMemory.FunctionalTests.csproj
index 3fd95d1cc652..000dd65ca229 100644
--- a/src/Servers/Kestrel/test/InMemory.FunctionalTests/InMemory.FunctionalTests.csproj
+++ b/src/Servers/Kestrel/test/InMemory.FunctionalTests/InMemory.FunctionalTests.csproj
@@ -17,7 +17,6 @@
-
diff --git a/src/Servers/Kestrel/test/InMemory.FunctionalTests/KestrelMetricsTests.cs b/src/Servers/Kestrel/test/InMemory.FunctionalTests/KestrelMetricsTests.cs
index ef415b5a0623..efa7e242aa78 100644
--- a/src/Servers/Kestrel/test/InMemory.FunctionalTests/KestrelMetricsTests.cs
+++ b/src/Servers/Kestrel/test/InMemory.FunctionalTests/KestrelMetricsTests.cs
@@ -273,7 +273,7 @@ public async Task Http1Connection_ServerShutdown_Abort()
var serviceContext = new TestServiceContext(LoggerFactory, metrics: new KestrelMetrics(testMeterFactory))
{
- MemoryPoolFactory = PinnedBlockMemoryPoolFactory.CreatePinnedBlockMemoryPool,
+ MemoryPoolFactory = new TestServiceContext.WrappingMemoryPoolFactory(() => TestMemoryPoolFactory.CreatePinnedBlockMemoryPool()),
ShutdownTimeout = TimeSpan.Zero
};
@@ -612,7 +612,7 @@ public async Task Http2Connection_ServerShutdown_Abort()
var serviceContext = new TestServiceContext(LoggerFactory, metrics: new KestrelMetrics(testMeterFactory))
{
ShutdownTimeout = TimeSpan.Zero,
- MemoryPoolFactory = PinnedBlockMemoryPoolFactory.CreatePinnedBlockMemoryPool
+ MemoryPoolFactory = new TestServiceContext.WrappingMemoryPoolFactory(() => TestMemoryPoolFactory.CreatePinnedBlockMemoryPool())
};
var tcs = new TaskCompletionSource(TaskCreationOptions.RunContinuationsAsynchronously);
diff --git a/src/Servers/Kestrel/test/InMemory.FunctionalTests/TestTransport/TestServer.cs b/src/Servers/Kestrel/test/InMemory.FunctionalTests/TestTransport/TestServer.cs
index 0ee3a41d48ec..e6d4f2edef29 100644
--- a/src/Servers/Kestrel/test/InMemory.FunctionalTests/TestTransport/TestServer.cs
+++ b/src/Servers/Kestrel/test/InMemory.FunctionalTests/TestTransport/TestServer.cs
@@ -70,7 +70,7 @@ public TestServer(RequestDelegate app, TestServiceContext context, Action
+
diff --git a/src/Servers/Kestrel/test/Sockets.FunctionalTests/TransportSelector.cs b/src/Servers/Kestrel/test/Sockets.FunctionalTests/TransportSelector.cs
index f5933759a282..0584487755c7 100644
--- a/src/Servers/Kestrel/test/Sockets.FunctionalTests/TransportSelector.cs
+++ b/src/Servers/Kestrel/test/Sockets.FunctionalTests/TransportSelector.cs
@@ -1,8 +1,6 @@
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.
-using System;
-using System.Buffers;
using Microsoft.AspNetCore.Hosting;
using Microsoft.Extensions.Hosting;
@@ -10,18 +8,15 @@ namespace Microsoft.AspNetCore.Server.Kestrel.FunctionalTests;
public static class TransportSelector
{
- public static IHostBuilder GetHostBuilder(Func> memoryPoolFactory = null,
- long? maxReadBufferSize = null)
+ public static IHostBuilder GetHostBuilder(long? maxReadBufferSize = null)
{
return new HostBuilder()
.ConfigureWebHost(webHostBuilder =>
{
- webHostBuilder
- .UseSockets(options =>
- {
- options.MemoryPoolFactory = memoryPoolFactory ?? options.MemoryPoolFactory;
- options.MaxReadBufferSize = maxReadBufferSize;
- });
+ webHostBuilder.UseSockets(options =>
+ {
+ options.MaxReadBufferSize = maxReadBufferSize;
+ });
});
}
}
diff --git a/src/Shared/Buffers.MemoryPool/DefaultMemoryPoolFactory.cs b/src/Shared/Buffers.MemoryPool/DefaultMemoryPoolFactory.cs
new file mode 100644
index 000000000000..347debba8212
--- /dev/null
+++ b/src/Shared/Buffers.MemoryPool/DefaultMemoryPoolFactory.cs
@@ -0,0 +1,66 @@
+// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+
+using System.Buffers;
+using System.Collections.Concurrent;
+using System.Diagnostics;
+using System.Diagnostics.Metrics;
+using Microsoft.AspNetCore.Connections;
+using Microsoft.Extensions.Logging;
+
+namespace Microsoft.AspNetCore;
+
+#nullable enable
+
+internal sealed class DefaultMemoryPoolFactory : IMemoryPoolFactory, IAsyncDisposable
+{
+ private readonly IMeterFactory? _meterFactory;
+ private readonly ConcurrentDictionary _pools = new();
+ private readonly PeriodicTimer _timer;
+ private readonly Task _timerTask;
+ private readonly ILogger? _logger;
+
+ public DefaultMemoryPoolFactory(IMeterFactory? meterFactory = null, ILogger? logger = null)
+ {
+ _meterFactory = meterFactory;
+ _logger = logger;
+ _timer = new PeriodicTimer(PinnedBlockMemoryPool.DefaultEvictionDelay);
+ _timerTask = Task.Run(async () =>
+ {
+ try
+ {
+ while (await _timer.WaitForNextTickAsync())
+ {
+ foreach (var pool in _pools.Keys)
+ {
+ pool.PerformEviction();
+ }
+ }
+ }
+ catch (Exception ex)
+ {
+ _logger?.LogCritical(ex, "Error while evicting memory from pools.");
+ }
+ });
+ }
+
+ public MemoryPool Create()
+ {
+ var pool = new PinnedBlockMemoryPool(_meterFactory, _logger);
+
+ _pools.TryAdd(pool, true);
+
+ pool.OnPoolDisposed(static (state, self) =>
+ {
+ ((ConcurrentDictionary)state!).TryRemove(self, out _);
+ }, _pools);
+
+ return pool;
+ }
+
+ public async ValueTask DisposeAsync()
+ {
+ _timer.Dispose();
+ await _timerTask;
+ }
+}
diff --git a/src/Shared/Buffers.MemoryPool/DiagnosticMemoryPool.cs b/src/Shared/Buffers.MemoryPool/DiagnosticMemoryPool.cs
index 2250dd045427..064b1ef8ceff 100644
--- a/src/Shared/Buffers.MemoryPool/DiagnosticMemoryPool.cs
+++ b/src/Shared/Buffers.MemoryPool/DiagnosticMemoryPool.cs
@@ -1,9 +1,10 @@
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.
+using System.Buffers;
using System.Linq;
-namespace System.Buffers;
+namespace Microsoft.AspNetCore;
///
/// Used to allocate and distribute re-usable blocks of memory.
diff --git a/src/Shared/Buffers.MemoryPool/DiagnosticPoolBlock.cs b/src/Shared/Buffers.MemoryPool/DiagnosticPoolBlock.cs
index d2c0dfd18917..411e98f9df00 100644
--- a/src/Shared/Buffers.MemoryPool/DiagnosticPoolBlock.cs
+++ b/src/Shared/Buffers.MemoryPool/DiagnosticPoolBlock.cs
@@ -1,12 +1,13 @@
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.
+using System.Buffers;
using System.Diagnostics;
using System.Runtime.InteropServices;
#nullable enable
-namespace System.Buffers;
+namespace Microsoft.AspNetCore;
///
/// Block tracking object used by the byte buffer memory pool. A slab is a large allocation which is divided into smaller blocks. The
diff --git a/src/Shared/Buffers.MemoryPool/MemoryPoolBlock.cs b/src/Shared/Buffers.MemoryPool/MemoryPoolBlock.cs
index abeca699c6b7..cf2233188c37 100644
--- a/src/Shared/Buffers.MemoryPool/MemoryPoolBlock.cs
+++ b/src/Shared/Buffers.MemoryPool/MemoryPoolBlock.cs
@@ -1,9 +1,10 @@
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.
+using System.Buffers;
using System.Runtime.InteropServices;
-namespace System.Buffers;
+namespace Microsoft.AspNetCore;
///
/// Wraps an array allocated in the pinned object heap in a reusable block of managed memory
diff --git a/src/Shared/Buffers.MemoryPool/MemoryPoolFactory.cs b/src/Shared/Buffers.MemoryPool/MemoryPoolFactory.cs
index 68880100c9c7..24679e1be23d 100644
--- a/src/Shared/Buffers.MemoryPool/MemoryPoolFactory.cs
+++ b/src/Shared/Buffers.MemoryPool/MemoryPoolFactory.cs
@@ -1,21 +1,26 @@
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.
-namespace System.Buffers;
+using System.Buffers;
+using System.Diagnostics.Metrics;
-internal static class PinnedBlockMemoryPoolFactory
+namespace Microsoft.AspNetCore;
+
+#nullable enable
+
+internal static class TestMemoryPoolFactory
{
- public static MemoryPool Create()
+ public static MemoryPool Create(IMeterFactory? meterFactory = null)
{
#if DEBUG
- return new DiagnosticMemoryPool(CreatePinnedBlockMemoryPool());
+ return new DiagnosticMemoryPool(CreatePinnedBlockMemoryPool(meterFactory));
#else
- return CreatePinnedBlockMemoryPool();
+ return CreatePinnedBlockMemoryPool(meterFactory);
#endif
}
- public static MemoryPool CreatePinnedBlockMemoryPool()
+ public static MemoryPool CreatePinnedBlockMemoryPool(IMeterFactory? meterFactory = null)
{
- return new PinnedBlockMemoryPool();
+ return new PinnedBlockMemoryPool(meterFactory);
}
}
diff --git a/src/Shared/Buffers.MemoryPool/MemoryPoolThrowHelper.cs b/src/Shared/Buffers.MemoryPool/MemoryPoolThrowHelper.cs
index de41f133a1a2..036f00798237 100644
--- a/src/Shared/Buffers.MemoryPool/MemoryPoolThrowHelper.cs
+++ b/src/Shared/Buffers.MemoryPool/MemoryPoolThrowHelper.cs
@@ -5,7 +5,7 @@
using System.Runtime.CompilerServices;
using System.Text;
-namespace System.Buffers;
+namespace Microsoft.AspNetCore;
internal sealed class MemoryPoolThrowHelper
{
diff --git a/src/Shared/Buffers.MemoryPool/PinnedBlockMemoryPool.cs b/src/Shared/Buffers.MemoryPool/PinnedBlockMemoryPool.cs
index 6c74b477c821..4e6be0f91052 100644
--- a/src/Shared/Buffers.MemoryPool/PinnedBlockMemoryPool.cs
+++ b/src/Shared/Buffers.MemoryPool/PinnedBlockMemoryPool.cs
@@ -1,22 +1,31 @@
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.
+using System.Buffers;
using System.Collections.Concurrent;
+using System.Diagnostics.Metrics;
+using Microsoft.Extensions.Logging;
#nullable enable
-namespace System.Buffers;
+namespace Microsoft.AspNetCore;
///
/// Used to allocate and distribute re-usable blocks of memory.
///
-internal sealed class PinnedBlockMemoryPool : MemoryPool
+internal sealed class PinnedBlockMemoryPool : MemoryPool, IThreadPoolWorkItem
{
///
/// The size of a block. 4096 is chosen because most operating systems use 4k pages.
///
private const int _blockSize = 4096;
+ // 10 seconds chosen arbitrarily. Trying to avoid running eviction too frequently
+ // to avoid trashing if the server gets batches of requests, but want to run often
+ // enough to avoid memory bloat if the server is idle for a while.
+ // This can be tuned later if needed.
+ public static readonly TimeSpan DefaultEvictionDelay = TimeSpan.FromSeconds(10);
+
///
/// Max allocation block size for pooled blocks,
/// larger values can be leased but they will be disposed after use rather than returned to the pool.
@@ -39,13 +48,41 @@ internal sealed class PinnedBlockMemoryPool : MemoryPool
///
private bool _isDisposed; // To detect redundant calls
+ private readonly PinnedBlockMemoryPoolMetrics? _metrics;
+ private readonly ILogger? _logger;
+
+ private long _currentMemory;
+ private long _evictedMemory;
+ private DateTimeOffset _nextEviction = DateTime.UtcNow.Add(DefaultEvictionDelay);
+
+ private uint _rentCount;
+ private uint _returnCount;
+
private readonly object _disposeSync = new object();
+ private Action