From 5b6403f266752bd7453cb632281a870a2929e64e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E6=9D=A8=E5=9B=BD=E9=94=8B?= Date: Wed, 18 Mar 2026 20:16:09 +0800 Subject: [PATCH] =?UTF-8?q?=F0=9F=90=9B=20fix(update):=20=E4=BF=AE?= =?UTF-8?q?=E5=A4=8D=20Win10=20=E8=87=AA=E5=8A=A8=E6=9B=B4=E6=96=B0?= =?UTF-8?q?=E6=97=B6=E6=96=87=E4=BB=B6=E8=A2=AB=E5=8D=A0=E7=94=A8=E5=AF=BC?= =?UTF-8?q?=E8=87=B4=E6=9B=BF=E6=8D=A2=E5=A4=B1=E8=B4=A5?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 冷却期:进程退出后增加 3 秒等待,确保 Win10 内核释放 exe 文件句柄 - 替换策略:新增 rename-before-replace 机制,先重命名旧文件再复制新文件 - 退避重试:替换固定 1 秒间隔为指数退避(1s→2s→3s→5s),总等待约 36 秒 - 残留清理:替换成功后删除 .old 残留文件 - 测试覆盖:新增 TestBuildWindowsScriptWin10Fixes 验证全部修复点 --- internal/app/methods_update.go | 27 +++++++++++++-- .../app/methods_update_windows_script_test.go | 34 +++++++++++++++++++ 2 files changed, 59 insertions(+), 2 deletions(-) diff --git a/internal/app/methods_update.go b/internal/app/methods_update.go index ae2bdfe0..240f4468 100644 --- a/internal/app/methods_update.go +++ b/internal/app/methods_update.go @@ -957,8 +957,25 @@ if %ERRORLEVEL%==0 ( ) call :log host process exited +rem -- Win10 needs extra time for kernel to release exe file handles -- +timeout /t 3 /nobreak >nul +call :log cooldown finished, starting file replace + set /a RETRY=0 :move_retry +call :log attempt !RETRY!: trying rename-then-copy strategy +ren "%TARGET%" "%TARGET_NAME%.old" >> "%LOG_FILE%" 2>&1 +if %ERRORLEVEL%==0 ( + copy /Y "%SOURCE_EXE%" "%TARGET%" >> "%LOG_FILE%" 2>&1 + if !ERRORLEVEL!==0 ( + del /F /Q "%TARGET%.old" >> "%LOG_FILE%" 2>&1 + goto move_done + ) + call :log copy after rename failed, restoring old file + ren "%TARGET_NAME%.old" "%TARGET_NAME%" >> "%LOG_FILE%" 2>&1 +) + +call :log rename strategy failed, trying direct move move /Y "%SOURCE_EXE%" "%TARGET%" >> "%LOG_FILE%" 2>&1 if %ERRORLEVEL%==0 goto move_done @@ -966,8 +983,13 @@ copy /Y "%SOURCE_EXE%" "%TARGET%" >> "%LOG_FILE%" 2>&1 if %ERRORLEVEL%==0 goto move_done set /a RETRY+=1 -if !RETRY! LSS 20 ( - timeout /t 1 /nobreak >nul +if !RETRY! LSS 15 ( + set /a WAIT=1 + if !RETRY! GEQ 3 set /a WAIT=2 + if !RETRY! GEQ 6 set /a WAIT=3 + if !RETRY! GEQ 9 set /a WAIT=5 + call :log waiting !WAIT! seconds before retry + timeout /t !WAIT! /nobreak >nul goto move_retry ) @@ -975,6 +997,7 @@ call :log replace failed after retries (portable mode, no elevation): check dire exit /b 1 :move_done +del /F /Q "%TARGET%.old" >> "%LOG_FILE%" 2>&1 start "" "%TARGET%" >> "%LOG_FILE%" 2>&1 if %ERRORLEVEL% NEQ 0 ( call :log cmd start failed, trying powershell Start-Process diff --git a/internal/app/methods_update_windows_script_test.go b/internal/app/methods_update_windows_script_test.go index 9313497b..2dcbb516 100644 --- a/internal/app/methods_update_windows_script_test.go +++ b/internal/app/methods_update_windows_script_test.go @@ -38,3 +38,37 @@ func TestBuildWindowsScriptKeepsBatchForSyntax(t *testing.T) { } } } + +func TestBuildWindowsScriptWin10Fixes(t *testing.T) { + script := buildWindowsScript( + `C:\tmp\GoNavi-v0.5.0-windows-amd64.exe`, + `C:\Program Files\GoNavi\GoNavi.exe`, + `C:\Program Files\GoNavi\.gonavi-update-windows-v0.5.0`, + `C:\Program Files\GoNavi\logs\update-install.log`, + 99999, + ) + + // 验证 Win10 关键修复点 + win10Fixes := []struct { + desc string + token string + }{ + {"cooldown after process exit", `timeout /t 3 /nobreak >nul`}, + {"cooldown log", `call :log cooldown finished, starting file replace`}, + {"rename-before-replace strategy", `ren "%TARGET%" "%TARGET_NAME%.old"`}, + {"copy after rename", `copy /Y "%SOURCE_EXE%" "%TARGET%"`}, + {"restore on copy failure", `ren "%TARGET_NAME%.old" "%TARGET_NAME%"`}, + {"direct move fallback", `call :log rename strategy failed, trying direct move`}, + {"exponential backoff tier 1", `if !RETRY! GEQ 3 set /a WAIT=2`}, + {"exponential backoff tier 2", `if !RETRY! GEQ 6 set /a WAIT=3`}, + {"exponential backoff tier 3", `if !RETRY! GEQ 9 set /a WAIT=5`}, + {"retry limit 15", `if !RETRY! LSS 15`}, + {"cleanup old file", `del /F /Q "%TARGET%.old"`}, + } + for _, fix := range win10Fixes { + if !strings.Contains(script, fix.token) { + t.Errorf("Win10 fix missing [%s]: expected token: %s", fix.desc, fix.token) + } + } +} +