Skip to content

Add IMemoryPoolFactory and cleanup memory pool while idle #61554

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Draft
wants to merge 12 commits into
base: main
Choose a base branch
from
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
18 changes: 18 additions & 0 deletions src/Servers/Connections.Abstractions/src/IMemoryPoolFactory.cs
Original file line number Diff line number Diff line change
@@ -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;

/// <summary>
///
/// </summary>
public interface IMemoryPoolFactory<T>
{
/// <summary>
///
/// </summary>
/// <returns></returns>
MemoryPool<T> Create();
}
Original file line number Diff line number Diff line change
@@ -1 +1,3 @@
#nullable enable
Microsoft.AspNetCore.Connections.IMemoryPoolFactory<T>
Microsoft.AspNetCore.Connections.IMemoryPoolFactory<T>.Create() -> System.Buffers.MemoryPool<T>!
Original file line number Diff line number Diff line change
@@ -1 +1,3 @@
#nullable enable
Microsoft.AspNetCore.Connections.IMemoryPoolFactory<T>
Microsoft.AspNetCore.Connections.IMemoryPoolFactory<T>.Create() -> System.Buffers.MemoryPool<T>!
Original file line number Diff line number Diff line change
@@ -1 +1,3 @@
#nullable enable
Microsoft.AspNetCore.Connections.IMemoryPoolFactory<T>
Microsoft.AspNetCore.Connections.IMemoryPoolFactory<T>.Create() -> System.Buffers.MemoryPool<T>!
Original file line number Diff line number Diff line change
@@ -1 +1,3 @@
#nullable enable
Microsoft.AspNetCore.Connections.IMemoryPoolFactory<T>
Microsoft.AspNetCore.Connections.IMemoryPoolFactory<T>.Create() -> System.Buffers.MemoryPool<T>!
15 changes: 13 additions & 2 deletions src/Servers/HttpSys/src/HttpSysListener.cs
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,8 @@

using System.Buffers;
using System.Diagnostics;
using System.Diagnostics.Metrics;
using Microsoft.AspNetCore.Connections;
using Microsoft.AspNetCore.Http;
using Microsoft.AspNetCore.HttpSys.Internal;
using Microsoft.AspNetCore.WebUtilities;
Expand Down Expand Up @@ -33,7 +35,14 @@ internal sealed partial class HttpSysListener : IDisposable
// 0.5 seconds per request. Respond with a 400 Bad Request.
private const int UnknownHeaderLimit = 1000;

internal MemoryPool<byte> MemoryPool { get; } = PinnedBlockMemoryPoolFactory.Create();
internal sealed class NoopMeterFactory : IMeterFactory
{
public Meter Create(MeterOptions options) => new Meter(options);

public void Dispose() { }
}

internal MemoryPool<byte> MemoryPool { get; }

private volatile State _state; // m_State is set only within lock blocks, but often read outside locks.

Expand All @@ -44,7 +53,7 @@ internal sealed partial class HttpSysListener : IDisposable

private readonly object _internalLock;

public HttpSysListener(HttpSysOptions options, ILoggerFactory loggerFactory)
public HttpSysListener(HttpSysOptions options, IMemoryPoolFactory<byte> memoryPoolFactory, ILoggerFactory loggerFactory)
{
ArgumentNullException.ThrowIfNull(options);
ArgumentNullException.ThrowIfNull(loggerFactory);
Expand All @@ -54,6 +63,8 @@ public HttpSysListener(HttpSysOptions options, ILoggerFactory loggerFactory)
throw new PlatformNotSupportedException();
}

MemoryPool = memoryPoolFactory.Create();

Options = options;

Logger = loggerFactory.CreateLogger<HttpSysListener>();
Expand Down
6 changes: 4 additions & 2 deletions src/Servers/HttpSys/src/MessagePump.cs
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand All @@ -27,12 +28,13 @@ internal sealed partial class MessagePump : IServer, IServerDelegationFeature

private readonly ServerAddressesFeature _serverAddresses;

public MessagePump(IOptions<HttpSysOptions> options, ILoggerFactory loggerFactory, IAuthenticationSchemeProvider authentication)
public MessagePump(IOptions<HttpSysOptions> options, IMemoryPoolFactory<byte> 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<MessagePump>();

if (_options.Authentication.Schemes != AuthenticationSchemes.None)
Expand Down
5 changes: 5 additions & 0 deletions src/Servers/HttpSys/src/WebHostBuilderHttpSysExtensions.cs
Original file line number Diff line number Diff line change
@@ -1,10 +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.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;
Expand Down Expand Up @@ -45,6 +48,8 @@ public static IWebHostBuilder UseHttpSys(this IWebHostBuilder hostBuilder)
};
});
services.AddAuthenticationCore();

services.TryAddSingleton<IMemoryPoolFactory<byte>, DefaultMemoryPoolFactory>();
});
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down Expand Up @@ -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<HttpSysException>(() => listener.Start());

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down Expand Up @@ -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();
Expand Down Expand Up @@ -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;
Expand All @@ -86,7 +87,7 @@ internal static HttpSysListener CreateServer(Action<HttpSysOptions> 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;
}
Expand Down
5 changes: 3 additions & 2 deletions src/Servers/HttpSys/test/FunctionalTests/Utilities.cs
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down Expand Up @@ -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<HttpSysOptions> 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<HttpSysOptions> configureOptions, RequestDelegate app, ILoggerFactory loggerFactory)
Expand Down
5 changes: 3 additions & 2 deletions src/Servers/HttpSys/test/NonHelixTests/Utilities.cs
Original file line number Diff line number Diff line change
@@ -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;
Expand Down Expand Up @@ -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<HttpSysOptions> 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<HttpSysOptions> configureOptions, RequestDelegate app)
Expand Down
5 changes: 4 additions & 1 deletion src/Servers/IIS/IIS/src/Core/IISHttpServer.cs
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand All @@ -21,7 +22,7 @@ internal sealed class IISHttpServer : IServer
private const string WebSocketVersionString = "WEBSOCKET_VERSION";

private IISContextFactory? _iisContextFactory;
private readonly MemoryPool<byte> _memoryPool = new PinnedBlockMemoryPool();
private readonly MemoryPool<byte> _memoryPool;
private GCHandle _httpServerHandle;
private readonly IHostApplicationLifetime _applicationLifetime;
private readonly ILogger<IISHttpServer> _logger;
Expand Down Expand Up @@ -60,10 +61,12 @@ public IISHttpServer(
IHostApplicationLifetime applicationLifetime,
IAuthenticationSchemeProvider authentication,
IConfiguration configuration,
IMemoryPoolFactory<byte> memoryPoolFactory,
IOptions<IISServerOptions> options,
ILogger<IISHttpServer> logger
)
{
_memoryPool = memoryPoolFactory.Create();
_nativeApplication = nativeApplication;
_applicationLifetime = applicationLifetime;
_logger = logger;
Expand Down
5 changes: 5 additions & 0 deletions src/Servers/IIS/IIS/src/WebHostBuilderIISExtensions.cs
Original file line number Diff line number Diff line change
@@ -1,11 +1,14 @@
// 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.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;

Expand Down Expand Up @@ -53,6 +56,8 @@ public static IWebHostBuilder UseIIS(this IWebHostBuilder hostBuilder)
options.IisMaxRequestSizeLimit = iisConfigData.maxRequestBodySize;
}
);

services.TryAddSingleton<IMemoryPoolFactory<byte>, DefaultMemoryPoolFactory>();
});
}

Expand Down
10 changes: 6 additions & 4 deletions src/Servers/Kestrel/Core/src/Internal/KestrelServerImpl.cs
Original file line number Diff line number Diff line change
Expand Up @@ -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<IHeartbeatHandler> heartbeatHandlers)
: this(transportFactories, multiplexedFactories, httpsConfigurationService, CreateServiceContext(options, loggerFactory, diagnosticSource, metrics, heartbeatHandlers))
{
}

Expand Down Expand Up @@ -73,7 +74,8 @@ internal KestrelServerImpl(
_transportManager = new TransportManager(_transportFactories, _multiplexedTransportFactories, _httpsConfigurationService, ServiceContext);
}

private static ServiceContext CreateServiceContext(IOptions<KestrelServerOptions> options, ILoggerFactory loggerFactory, DiagnosticSource? diagnosticSource, KestrelMetrics metrics)
private static ServiceContext CreateServiceContext(IOptions<KestrelServerOptions> options, ILoggerFactory loggerFactory, DiagnosticSource? diagnosticSource, KestrelMetrics metrics,
IEnumerable<IHeartbeatHandler> heartbeatHandlers)
{
ArgumentNullException.ThrowIfNull(options);
ArgumentNullException.ThrowIfNull(loggerFactory);
Expand All @@ -87,7 +89,7 @@ private static ServiceContext CreateServiceContext(IOptions<KestrelServerOptions
var dateHeaderValueManager = new DateHeaderValueManager(TimeProvider.System);

var heartbeat = new Heartbeat(
new IHeartbeatHandler[] { dateHeaderValueManager, connectionManager },
[ dateHeaderValueManager, connectionManager, ..heartbeatHandlers ],
TimeProvider.System,
DebuggerWrapper.Singleton,
trace,
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
// 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.AspNetCore.Connections;
using Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Infrastructure;

namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal;

internal sealed class PinnedBlockMemoryPoolFactory : IMemoryPoolFactory<byte>, IHeartbeatHandler
{
private readonly IMeterFactory _meterFactory;
private readonly TimeProvider _timeProvider;
private readonly ConcurrentDictionary<PinnedBlockMemoryPool, PinnedBlockMemoryPool> _pools = new();

public PinnedBlockMemoryPoolFactory(IMeterFactory meterFactory, TimeProvider? timeProvider = null)
{
_timeProvider = timeProvider ?? TimeProvider.System;
_meterFactory = meterFactory;
}

public MemoryPool<byte> Create()
{
var pool = new PinnedBlockMemoryPool(_meterFactory);

_pools.TryAdd(pool, pool);

pool.OnPoolDisposed(static (state, self) =>
{
((ConcurrentDictionary<PinnedBlockMemoryPool, PinnedBlockMemoryPool>)state!).TryRemove(self, out _);
}, _pools);

return pool;
}

public void OnHeartbeat()
{
var now = _timeProvider.GetUtcNow();
foreach (var pool in _pools)
{
pool.Value.TryScheduleEviction(now);
}
}
}
3 changes: 2 additions & 1 deletion src/Servers/Kestrel/Core/src/KestrelServer.cs
Original file line number Diff line number Diff line change
Expand Up @@ -37,7 +37,8 @@ public KestrelServer(IOptions<KestrelServerOptions> options, IConnectionListener
new SimpleHttpsConfigurationService(),
loggerFactory,
diagnosticSource: null,
new KestrelMetrics(new DummyMeterFactory()));
new KestrelMetrics(new DummyMeterFactory()),
heartbeatHandlers: []);
}

/// <inheritdoc />
Expand Down
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
<Project Sdk="Microsoft.NET.Sdk">
<Project Sdk="Microsoft.NET.Sdk">

<PropertyGroup>
<Description>Core components of ASP.NET Core Kestrel cross-platform web server.</Description>
Expand Down Expand Up @@ -37,6 +37,7 @@
<Compile Include="$(SharedSourceRoot)Obsoletions.cs" LinkBase="Shared" />
<Compile Include="$(RepoRoot)src\Shared\TaskToApm.cs" Link="Internal\TaskToApm.cs" />
<Compile Include="$(SharedSourceRoot)Metrics\MetricsExtensions.cs" />
<Compile Include="$(RepoRoot)src\Shared\Buffers.MemoryPool\*.cs" LinkBase="MemoryPool" />
</ItemGroup>

<ItemGroup>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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);

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,7 @@ public class Http1OutputProducerTests : IDisposable

public Http1OutputProducerTests()
{
_memoryPool = PinnedBlockMemoryPoolFactory.Create();
_memoryPool = TestMemoryPoolFactory.Create();
}

public void Dispose()
Expand Down
Loading
Loading