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 的超时兜底
}
}
}