ci/woodpecker/push/main-amd64-build Pipeline was successful
Namespaces, assemblies, solution, binaries, images, systemd units, config paths, environment variables, protocol methods and the Linux user are all renamed. Breaking: existing databases, node identities and installed units must be recreated.
50 lines
1.7 KiB
C#
50 lines
1.7 KiB
C#
using Fleet.Server;
|
||
using Microsoft.Extensions.DependencyInjection;
|
||
using Microsoft.Extensions.Hosting;
|
||
|
||
namespace MoShiro.Backend.Gateway;
|
||
|
||
/// <summary>
|
||
/// 宿主开始停止时先关闭所有 Agent 的长连接(Close 握手,不发 revoked:Agent 会重连)。
|
||
/// 否则 Kestrel 的有序停止要等满 30 秒的超时,才会放弃这些永远不会自己结束的 WebSocket。
|
||
/// </summary>
|
||
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<IFleetConnections>().CloseAllAsync("server shutting down");
|
||
}
|
||
catch (Exception ex) when (ex is ObjectDisposedException or InvalidOperationException)
|
||
{
|
||
// 停止过程中,服务已经在释放:Kestrel 的超时兜底
|
||
}
|
||
}
|
||
}
|