using Fleet.Server; using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.Hosting; namespace MoShiro.Backend.Gateway; /// /// 宿主开始停止时先关闭所有 Agent 的长连接(Close 握手,不发 revoked:Agent 会重连)。 /// 否则 Kestrel 的有序停止要等满 30 秒的超时,才会放弃这些永远不会自己结束的 WebSocket。 /// public sealed class ConnectionShutdown : IHostedService { private readonly IServiceProvider _services; private readonly IHostApplicationLifetime _lifetime; private Task? _closing; public ConnectionShutdown(IServiceProvider services, IHostApplicationLifetime lifetime) { _services = services; _lifetime = lifetime; } public Task StartAsync(CancellationToken cancellationToken) { // ApplicationStopping 在有序停止的最开始触发,早于 Kestrel 等待连接结束:这里不阻塞,关闭在后台进行,StopAsync 再等它做完 _lifetime.ApplicationStopping.Register(() => _closing = CloseAsync()); return Task.CompletedTask; } public async Task StopAsync(CancellationToken cancellationToken) { if (_closing is { } closing) { await closing.WaitAsync(TimeSpan.FromSeconds(5), cancellationToken); } } private async Task CloseAsync() { try { await _services.GetRequiredService().CloseAllAsync("server shutting down"); } catch (Exception ex) when (ex is ObjectDisposedException or InvalidOperationException) { // 停止过程中,服务已经在释放:Kestrel 的超时兜底 } } }