| File: Transport\SocketConnectionListener.cs | Web Access |
| Project: src\src\Aspire.Hosting.Native.Server\Aspire.Hosting.Native.Server.csproj (Aspire.Hosting.Native.Server) |
// Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. using System.Net.Sockets; namespace Aspire.Hosting.Native.Transport; /// <summary>Owns bounded concurrent socket connections independently of their application protocol.</summary> internal static class SocketConnectionListener { public static async Task RunAsync(Socket listener, int maximumConnections, Func<Stream, CancellationToken, Task> serve, CancellationToken cancellationToken) { ArgumentNullException.ThrowIfNull(listener); ArgumentNullException.ThrowIfNull(serve); ArgumentOutOfRangeException.ThrowIfLessThan(maximumConnections, 1); using var lifetime = CancellationTokenSource.CreateLinkedTokenSource(cancellationToken); var clients = new List<Task>(); try { while (!lifetime.IsCancellationRequested) { var socket = await listener.AcceptAsync(lifetime.Token).ConfigureAwait(false); try { // Reap after accept: a client may have ended while accept was pending. await Task.WhenAll(clients.Where(client => client.IsCompleted)).ConfigureAwait(false); clients.RemoveAll(client => client.IsCompletedSuccessfully); if (clients.Count >= maximumConnections) { socket.Dispose(); continue; } clients.Add(ServeAsync(socket)); } catch { socket.Dispose(); throw; } } } catch (OperationCanceledException) when (lifetime.IsCancellationRequested) { } finally { await lifetime.CancelAsync().ConfigureAwait(false); await Task.WhenAll(clients).ConfigureAwait(false); } async Task ServeAsync(Socket socket) { using var owned = socket; using var stream = new NetworkStream(socket, ownsSocket: false); try { await serve(stream, lifetime.Token).ConfigureAwait(false); } catch (OperationCanceledException) when (lifetime.IsCancellationRequested) { } catch { // Surface handler failures even when no further client connects. await lifetime.CancelAsync().ConfigureAwait(false); throw; } } } }