From fbc9f903dfb05aaee2db77d95696ed3bcbf9e11b Mon Sep 17 00:00:00 2001 From: HuaiYJ Date: Mon, 24 Aug 2026 16:21:13 +0800 Subject: [PATCH] fix: durably persist upgraded binary --- internal/upgrade/syncdir_unix.go | 17 +++++++++++++++++ internal/upgrade/syncdir_windows.go | 9 +++++++++ internal/upgrade/upgrader.go | 24 ++++++++++++++++++------ internal/upgrade/upgrader_test.go | 4 ++-- 4 files changed, 46 insertions(+), 8 deletions(-) create mode 100644 internal/upgrade/syncdir_unix.go create mode 100644 internal/upgrade/syncdir_windows.go diff --git a/internal/upgrade/syncdir_unix.go b/internal/upgrade/syncdir_unix.go new file mode 100644 index 0000000..20e5593 --- /dev/null +++ b/internal/upgrade/syncdir_unix.go @@ -0,0 +1,17 @@ +// Copyright (C) 2024 OpenCloudOS +// License: GPL-3.0-or-later + +//go:build !windows + +package upgrade + +import "os" + +func syncDirectory(path string) error { + dir, err := os.Open(path) + if err != nil { + return err + } + defer dir.Close() + return dir.Sync() +} diff --git a/internal/upgrade/syncdir_windows.go b/internal/upgrade/syncdir_windows.go new file mode 100644 index 0000000..aa5a7b1 --- /dev/null +++ b/internal/upgrade/syncdir_windows.go @@ -0,0 +1,9 @@ +// Copyright (C) 2024 OpenCloudOS +// License: GPL-3.0-or-later + +//go:build windows + +package upgrade + +// Windows 无法通过 os.File.Sync 可移植地同步目录句柄。 +func syncDirectory(string) error { return nil } diff --git a/internal/upgrade/upgrader.go b/internal/upgrade/upgrader.go index d818fe6..e178fbf 100644 --- a/internal/upgrade/upgrader.go +++ b/internal/upgrade/upgrader.go @@ -209,12 +209,25 @@ func (u *Upgrader) Execute(ctx context.Context, taskID string, action UpgradeAct defer cancel() } dlErr := u.client.DownloadChunks(dlCtx, action.MD5, action.ArtifactID, f) - // 在校验/重命名前关闭文件,使 Windows 风格的语义也能安全运行。 - _ = f.Close() if dlErr != nil { + _ = f.Close() _ = os.Remove(tmpPath) return u.reportErr(ctx, taskID, ReasonDownloadFailed, dlErr.Error()) } + if err := f.Chmod(0o755); err != nil { + _ = f.Close() + _ = os.Remove(tmpPath) + return u.reportErr(ctx, taskID, ReasonReplaceFailed, "chmod: "+err.Error()) + } + if err := f.Sync(); err != nil { + _ = f.Close() + _ = os.Remove(tmpPath) + return u.reportErr(ctx, taskID, ReasonReplaceFailed, "sync temp: "+err.Error()) + } + if err := f.Close(); err != nil { + _ = os.Remove(tmpPath) + return u.reportErr(ctx, taskID, ReasonReplaceFailed, "close temp: "+err.Error()) + } // ---------------- 3. Verify ---------------- got, err := fileMD5(tmpPath) @@ -229,14 +242,13 @@ func (u *Upgrader) Execute(ctx context.Context, taskID string, action UpgradeAct } // ---------------- 4. Replace ---------------- - if err := os.Chmod(tmpPath, 0o755); err != nil { - _ = os.Remove(tmpPath) - return u.reportErr(ctx, taskID, ReasonReplaceFailed, "chmod: "+err.Error()) - } if err := os.Rename(tmpPath, execPath); err != nil { _ = os.Remove(tmpPath) return u.reportErr(ctx, taskID, ReasonReplaceFailed, "rename: "+err.Error()) } + if err := syncDirectory(filepath.Dir(execPath)); err != nil { + return u.reportErr(ctx, taskID, ReasonReplaceFailed, "sync parent directory: "+err.Error()) + } // ---------------- 5. Report + Exit ---------------- now := time.Now().Unix() diff --git a/internal/upgrade/upgrader_test.go b/internal/upgrade/upgrader_test.go index bcb4f0e..f301760 100644 --- a/internal/upgrade/upgrader_test.go +++ b/internal/upgrade/upgrader_test.go @@ -346,10 +346,10 @@ func (g *gatedClient) DownloadChunks(ctx context.Context, _ string, _ string, w return err } -// waitForExit 轮询 exit 计数器最长 500ms。 +// waitForExit 给生产延迟留出一倍调度余量,避免回调与断言同在边界时误报。 func waitForExit(t *testing.T, c *atomic.Int32, want int32) { t.Helper() - deadline := time.Now().Add(500 * time.Millisecond) + deadline := time.Now().Add(2 * exitGracePeriod) for time.Now().Before(deadline) { if c.Load() >= want { return -- Gitee