From f9be98e8015c925143ea17d45d6232f04aa2cb15 Mon Sep 17 00:00:00 2001 From: JustMe Date: Sun, 18 Oct 2026 10:22:34 +0800 Subject: [PATCH] fix(hosting): exit on our own when the orderly shutdown hangs [CI] --- backend/fleet/src/Fleet.Hosting/Hosting.cs | 22 ++++++++++++++++++++++ 1 file changed, 22 insertions(+) diff --git a/backend/fleet/src/Fleet.Hosting/Hosting.cs b/backend/fleet/src/Fleet.Hosting/Hosting.cs index 1a5fe44..5a3a03e 100644 --- a/backend/fleet/src/Fleet.Hosting/Hosting.cs +++ b/backend/fleet/src/Fleet.Hosting/Hosting.cs @@ -156,6 +156,9 @@ public static class ServiceShell log.Info("stopping", ("result", "SIGHUP")); cts.Cancel(); }); + // 有序退出卡住(比如某个已经死掉的会话收不到对端的关闭回复)时不能一直挂着:supervisor 的 stop 超时之前自己退出,由它重新拉起 + using var exited = new CancellationTokenSource(); + using CancellationTokenRegistration deadline = cts.Token.Register(() => _ = ExitIfStuckAsync(time ?? TimeProvider.System, log, exited.Token)); Task trimmer = TrimHeapAsync(cts.Token); Task scheduled = restart is null ? Task.CompletedTask : WaitForRestartAsync(restart, time ?? TimeProvider.System, log, cts); try @@ -171,9 +174,28 @@ public static class ServiceShell await cts.CancelAsync(); await trimmer; await scheduled; + await exited.CancelAsync(); } } + /// 退出请求发出之后给有序退出的时间(要短于容器的 StopTimeout,否则 supervisor 会先强杀)。 + public static readonly TimeSpan ShutdownGrace = TimeSpan.FromSeconds(10); + + private static async Task ExitIfStuckAsync(TimeProvider time, INodeLog log, CancellationToken exited) + { + try + { + await Task.Delay(ShutdownGrace, time, exited); + } + catch (OperationCanceledException) + { + return; // 正常退出了 + } + + log.Warn("orderly shutdown did not finish in time, exiting", ("grace_s", (long)ShutdownGrace.TotalSeconds)); + Environment.Exit(0); + } + private static async Task WaitForRestartAsync(RestartController restart, TimeProvider time, INodeLog log, CancellationTokenSource cts) { while (!cts.IsCancellationRequested)