using Microsoft.Extensions.Hosting; namespace TanksServer.Interactivity; internal abstract class WebsocketServer( ILogger logger ) : IHostedLifecycleService, IDisposable where T : WebsocketServerConnection { private readonly SemaphoreSlim _mutex = new(1, 1); private bool _closing; private readonly HashSet _connections = []; public async Task StoppingAsync(CancellationToken cancellationToken) { logger.LogInformation("closing connections"); await LockedAsync(async () => { _closing = true; await Task.WhenAll(_connections.Select(c => c.CloseAsync())); }, cancellationToken); logger.LogInformation("closed connections"); } protected ValueTask ParallelForEachConnectionAsync(Func body) => LockedAsync(async () => await Task.WhenAll(_connections.Select(body)), CancellationToken.None); private ValueTask AddConnectionAsync(T connection) => LockedAsync(async () => { if (_closing) { logger.LogWarning("refusing connection because server is shutting down"); await connection.CloseAsync(); } _connections.Add(connection); }, CancellationToken.None); private ValueTask RemoveConnectionAsync(T connection) => LockedAsync(() => { _connections.Remove(connection); return ValueTask.CompletedTask; }, CancellationToken.None); protected async Task HandleClientAsync(T connection) { await AddConnectionAsync(connection); await connection.ReceiveAsync(); await RemoveConnectionAsync(connection); await connection.RemovedAsync(); } private async ValueTask LockedAsync(Func action, CancellationToken cancellationToken) { await _mutex.WaitAsync(cancellationToken); try { await action(); } finally { _mutex.Release(); } } public virtual void Dispose() => _mutex.Dispose(); public Task StartAsync(CancellationToken cancellationToken) => Task.CompletedTask; public Task StopAsync(CancellationToken cancellationToken) => Task.CompletedTask; public Task StartedAsync(CancellationToken cancellationToken) => Task.CompletedTask; public Task StartingAsync(CancellationToken cancellationToken) => Task.CompletedTask; public Task StoppedAsync(CancellationToken cancellationToken) => Task.CompletedTask; }