fix(update):修复升级回滚未等待信号的问题

This commit is contained in:
qian committed 2026-08-15 00:38:11 +08:00
1 parent 196b0d0b5e
commit 27f4770623
6 files changed
+78 -12

No files matched your search

+2 -1
View File
@@ -285,9 +285,10 @@ var (
// 1) 先排空任务池(StopSyncImageWorkers / StopAsyncPolling)——它们收尾时会写
// 用量日志 / 审计 / 结算;此时异步 Writer 必须仍存活。
// 2) 再 flush 关闭异步 Writer。
// 3) 最后关 webhook / plugin。
// 3) 最后关 webhook / 调度后台任务 / plugin。
// 因此把两个 task.Stop* 注册在最后(最先执行),Writer 关闭注册在前(后执行)。
defer plugin.Shutdown(ctx)
defer dispatchadapter.Shutdown()
defer tenant.ShutdownWebhookDispatcher()
defer common.CloseAuditLogWriter()
defer common.CloseChannelErrorWriter()
+48
View File
@@ -0,0 +1,48 @@
package update
import (
"context"
"os"
"time"
"github.com/gogf/gf/v2/frame/g"
)
const (
// exitResponseGrace 让 HTTP 响应先写出的等待时间
exitResponseGrace = 500 * time.Millisecond
// gracefulExitTimeout 优雅关闭链路的最长等待。需覆盖 cmd.go defer 链中
// sync-image 任务池的收尾等待(3 分钟),再留余量;超时则强制退出兜底,
// 防止个别长连接把退出拖到无限长。
gracefulExitTimeout = 4 * time.Minute
)
// gracefulExit 更新/回滚完成后的进程退出入口。
//
// 不能直接 os.Exit:那会跳过 cmd.go 中 s.Run() 之后的 defer 链(排空任务池、
// flush 用量/审计异步 Writer、退款在途任务),导致计费数据丢失。
// 正确做法是向自身发送 SIGTERM,走 GoFrame 的信号处理链路:
//
// SIGTERM → ghttp 关闭监听并等待在途请求 → s.Run() 返回
// → cmd.go defer 链执行(LIFO)→ main 返回,进程自然退出
//
// 由进程管理器(systemd Restart=always / supervisor 等)随后拉起新二进制。
// Windows 无自信号机制(且在线更新仅支持 Linux),退化为直接退出。
func gracefulExit(ctx context.Context, reason string) {
go func() {
time.Sleep(exitResponseGrace)
g.Log().Infof(ctx, "%s: requesting graceful shutdown", reason)
if !signalSelfTerm() {
// 平台不支持自信号:无优雅链路可用,直接退出(不阻塞调用方)
os.Exit(0)
}
// 优雅链路正常情况下会让 main 自然返回;若超过上限仍未退出
// (如 SSE 流长时间不结束),强制退出兜底
time.Sleep(gracefulExitTimeout)
g.Log().Warningf(ctx, "%s: graceful shutdown not finished within %v, forcing exit",
reason, gracefulExitTimeout)
os.Exit(0)
}()
}
@@ -0,0 +1,12 @@
//go:build !windows
package update
import (
"syscall"
)
// signalSelfTerm 向自身发送 SIGTERM,触发 GoFrame 的优雅关闭信号处理
func signalSelfTerm() bool {
return syscall.Kill(syscall.Getpid(), syscall.SIGTERM) == nil
}
@@ -0,0 +1,8 @@
//go:build windows
package update
// signalSelfTerm Windows 无自信号机制,返回 false 由调用方直接退出
func signalSelfTerm() bool {
return false
}
+4 -3
View File
@@ -153,10 +153,11 @@ func performUpdate(ctx context.Context, targetVersion, downloadURL, checksumURL
// Step 8: Restart
manager.setProgress(PhaseRestarting, "正在重启服务...", 90)
g.Log().Info(ctx, "Update complete, exiting for restart...")
time.Sleep(500 * time.Millisecond) // give status API a moment to respond
g.Log().Info(ctx, "Update complete, restarting...")
os.Exit(0)
// 走 SIGTERM 优雅关闭链路(见 gracefulExit 注释),让 cmd.go 的 defer 链
// 排空任务池并 flush 用量/审计日志后自然退出,由进程管理器拉起新版本
gracefulExit(ctx, "update complete")
}
// extractBinary extracts the team-api binary from a tar.gz archive to outputPath.
+4 -8
View File
@@ -6,7 +6,6 @@ import (
"fmt"
"os"
"path/filepath"
"time"
"github.com/gogf/gf/v2/frame/g"
@@ -70,13 +69,10 @@ func Rollback(ctx context.Context) error {
g.Log().Infof(ctx, "Rollback to %s complete, scheduling restart...", info.BackupVersion)
// 延迟退出:Rollback 在 HTTP handler 调用栈内同步执行,直接 os.Exit 会切断
// 尚未写出的响应(前端拿到连接重置而非成功提示)。先让 handler 返回响应,
// 稍后退出进程,由进程管理器(systemd/supervisor 等)拉起旧版本
go func() {
time.Sleep(500 * time.Millisecond)
os.Exit(0)
}()
// 走 SIGTERM 优雅退出(见 gracefulExit 注释):先让 handler 把"回滚已启动"
// 响应写出去,再触发优雅关闭链路排空任务池/异步 Writer 后自然退出,
// 由进程管理器(systemd/supervisor 等)拉起旧版本
gracefulExit(ctx, "rollback complete")
return nil
}