File: SocketConnectionContextFactory.cs
Web Access
Project: src\src\Servers\Kestrel\Transport.Sockets\src\Microsoft.AspNetCore.Server.Kestrel.Transport.Sockets.csproj (Microsoft.AspNetCore.Server.Kestrel.Transport.Sockets)
// 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.Pipelines;
using System.Net.Sockets;
using Microsoft.AspNetCore.Connections;
using Microsoft.AspNetCore.Server.Kestrel.Transport.Sockets.Internal;
using Microsoft.Extensions.Logging;
 
namespace Microsoft.AspNetCore.Server.Kestrel.Transport.Sockets;
 
/// <summary>
/// A factory for socket based connections contexts.
/// </summary>
public sealed class SocketConnectionContextFactory : IDisposable
{
    private readonly SocketConnectionFactoryOptions _options;
    private readonly ILogger _logger;
    private readonly int _settingsCount;
    private readonly QueueSettings[] _settings;
 
    // long to prevent overflow
    private long _settingsIndex;
 
    /// <summary>
    /// Creates the <see cref="SocketConnectionContextFactory"/>.
    /// </summary>
    /// <param name="options">The options.</param>
    /// <param name="logger">The logger.</param>
    public SocketConnectionContextFactory(SocketConnectionFactoryOptions options, ILogger logger)
    {
        ArgumentNullException.ThrowIfNull(options);
        ArgumentNullException.ThrowIfNull(logger);
 
        _options = options;
        _logger = logger;
        _settingsCount = _options.IOQueueCount;
 
        var maxReadBufferSize = _options.MaxReadBufferSize ?? 0;
        var maxWriteBufferSize = _options.MaxWriteBufferSize ?? 0;
        var applicationScheduler = options.UnsafePreferInlineScheduling ? PipeScheduler.Inline : PipeScheduler.ThreadPool;
 
        if (_settingsCount > 0)
        {
            _settings = new QueueSettings[_settingsCount];
 
            for (var i = 0; i < _settingsCount; i++)
            {
                var memoryPool = _options.MemoryPoolFactory();
                var transportScheduler = options.UnsafePreferInlineScheduling ? PipeScheduler.Inline : new IOQueue();
 
                _settings[i] = new QueueSettings()
                {
                    Scheduler = transportScheduler,
                    InputOptions = new PipeOptions(memoryPool, applicationScheduler, transportScheduler, maxReadBufferSize, maxReadBufferSize / 2, useSynchronizationContext: false),
                    OutputOptions = new PipeOptions(memoryPool, transportScheduler, applicationScheduler, maxWriteBufferSize, maxWriteBufferSize / 2, useSynchronizationContext: false),
                    SocketSenderPool = new SocketSenderPool(PipeScheduler.Inline),
                    MemoryPool = memoryPool,
                };
            }
        }
        else
        {
            var memoryPool = _options.MemoryPoolFactory();
            var transportScheduler = options.UnsafePreferInlineScheduling ? PipeScheduler.Inline : PipeScheduler.ThreadPool;
 
            _settings = new QueueSettings[]
            {
                new QueueSettings()
                {
                    Scheduler = transportScheduler,
                    InputOptions = new PipeOptions(memoryPool, applicationScheduler, transportScheduler, maxReadBufferSize, maxReadBufferSize / 2, useSynchronizationContext: false),
                    OutputOptions = new PipeOptions(memoryPool, transportScheduler, applicationScheduler, maxWriteBufferSize, maxWriteBufferSize / 2, useSynchronizationContext: false),
                    SocketSenderPool = new SocketSenderPool(PipeScheduler.Inline),
                    MemoryPool = memoryPool,
                }
            };
            _settingsCount = 1;
        }
    }
 
    /// <summary>
    /// Create a <see cref="ConnectionContext"/> for a socket.
    /// </summary>
    /// <param name="socket">The socket for the connection.</param>
    /// <returns></returns>
    public ConnectionContext Create(Socket socket)
    {
        var setting = _settings[Interlocked.Increment(ref _settingsIndex) % _settingsCount];
 
        var connection = new SocketConnection(socket,
            setting.MemoryPool,
            setting.SocketSenderPool.Scheduler,
            _logger,
            setting.SocketSenderPool,
            setting.InputOptions,
            setting.OutputOptions,
            waitForData: _options.WaitForDataBeforeAllocatingBuffer,
            finOnError: _options.FinOnError);
 
        connection.Start();
        return connection;
    }
 
    /// <inheritdoc />
    public void Dispose()
    {
        // Dispose any pooled senders and memory pools
        foreach (var setting in _settings)
        {
            setting.SocketSenderPool.Dispose();
            setting.MemoryPool.Dispose();
        }
    }
 
    private sealed class QueueSettings
    {
        public PipeScheduler Scheduler { get; init; } = default!;
        public PipeOptions InputOptions { get; init; } = default!;
        public PipeOptions OutputOptions { get; init; } = default!;
        public SocketSenderPool SocketSenderPool { get; init; } = default!;
        public MemoryPool<byte> MemoryPool { get; init; } = default!;
    }
}