Skip to content
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>
/// Interface for creating memory pools.
/// </summary>
public interface IMemoryPoolFactory<T>
{
/// <summary>
/// Creates a new instance of a memory pool.
/// </summary>
/// <returns>A new memory pool instance.</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>!
7 changes: 5 additions & 2 deletions src/Servers/HttpSys/src/HttpSysListener.cs
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down Expand Up @@ -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<byte> MemoryPool { get; } = PinnedBlockMemoryPoolFactory.Create();
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 +45,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 +55,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
4 changes: 4 additions & 0 deletions src/Servers/HttpSys/src/WebHostBuilderHttpSysExtensions.cs
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down Expand Up @@ -45,6 +47,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
4 changes: 4 additions & 0 deletions src/Servers/IIS/IIS/src/WebHostBuilderIISExtensions.cs
Original file line number Diff line number Diff line change
Expand Up @@ -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;

Expand Down Expand Up @@ -53,6 +55,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,47 @@
// 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;
// micro-optimization: Using nuint as the value type to avoid GC write barriers; could replace with ConcurrentHashSet if that becomes available
private readonly ConcurrentDictionary<PinnedBlockMemoryPool, nuint> _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, nuint.Zero);

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

return pool;
}

public void OnHeartbeat()
{
var now = _timeProvider.GetUtcNow();
foreach (var pool in _pools)
{
pool.Key.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
@@ -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;
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
Loading
Loading