Conversation
Windows 允许重命名正在运行的 exe(实测 `MoveFileW` 返回 0),所以 `replace_target_with_backup` 的 rename + 拷贝新文件**能在旧实例仍然存活时跑完**, 而原来的 `let _ = remove_file_if_exists(&backup_path)` 恰好丢掉了"旧进程是否已退出" 的唯一信号:helper 替换完立刻 `restart_application()`,新版本在旧实例仍持有 Windows 单实例管道时启动,就会走进"转发给已有实例"分支、拿到确认后立刻退出,用户看到的是 "更新完成后应用不再出现"。 运行中的 exe 无法被删除(实测 `DeleteFileW` 返回 `ERROR_ACCESS_DENIED(5)`,进程 退出后转为成功),因此"备份文件变成可删除"就是旧进程已退出的可靠探针。新增 `wait_for_previous_instance_exit()`:有界等待(30s / 200ms 轮询)后再拉起新版本, 超时只记录并继续,避免旧进程异常不退时把更新卡在替换之后;首次安装(无备份) 不等待。 "仍被占用"按**原始错误码**判定(`file_is_still_in_use()`),不能用 `ErrorKind`: std 把 5 映射成 `PermissionDenied`,却把 32 映射成 `Uncategorized`,按 kind 判会漏掉 `ERROR_SHARING_VIOLATION`。这与单实例模块按原始码分类的理由一致。 这条链路是单实例门禁收紧后才暴露的:在此之前转发恒失败、门禁放行,新版本反而 "能起来",掩盖了旧实例与新版本并存的时序问题。 验证: - `cargo test -p main --bin navop --config profile.dev.package.main.debug=0`:585 passed / 1 failed,测试本体 3.33s。唯一失败是**既有**的 `home_tab::tests::rendering::recent_section_does_not_participate_in_search`(纯源文本契约, 输入 `main/src/home_tab/content.rs` 未被本提交触碰;它 split 的函数实际在 `home_layout.rs`, 上游已改断言),本分支基点即为红,与本改动无关。本提交新增的 `restart_waits_until_the_previous_instance_releases_the_executable` 通过。 - 独立探针 `.workbuddy/tmp/share-mode-probe/`(std-only 独立 crate,与产品逻辑同构, 秒级编译):3/3 PASS —— 备份被占用时消耗完整个 timeout 且不删除(410ms/400ms)、 释放后 819µs 内放行并清理备份、无旧文件时不等待(132µs);并复现 std 的错误码映射 `raw 5 -> PermissionDenied` / `raw 32 -> Uncategorized`(按 `ErrorKind` 判会漏掉 32)。 - 原始文件语义探针 `.workbuddy/tmp/update_race_probe_raw.py`(裸 `MoveFileW` / `DeleteFileW`):运行中的 exe 可重命名(winerror=0)、不可删除 (`ERROR_ACCESS_DENIED=5`)、进程退出后删除成功。 - 未验证:真机跑一次完整更新安装(需要一个真实 release 包替换正在运行的 exe, 无法在不干扰用户真实安装的前提下构造)。
超时(10s,原 30s)或探测失败统一视为「无法确认旧实例已退出」,不再拉起新版本:新版本在旧实例仍占着单实例管道时启动,要么转发后自己退出(用户看不到任何窗口),要么弹「启动请求没能交给它」。改为弹系统对话框提示用户结束 Navop 进程后手动启动——更新 helper 没有窗口,release 又是 windows_subsystem = windows,eprintln 用户看不到。
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Windows 允许重命名正在运行的 exe(实测
MoveFileW返回 0),所以replace_target_with_backup的 rename + 拷贝新文件能在旧实例仍然存活时跑完,而原来的
let _ = remove_file_if_exists(&backup_path)恰好丢掉了"旧进程是否已退出"的唯一信号:helper 替换完立刻
restart_application(),新版本在旧实例仍持有 Windows单实例管道时启动,就会走进"转发给已有实例"分支、拿到确认后立刻退出,用户看到的是
"更新完成后应用不再出现"。
运行中的 exe 无法被删除(实测
DeleteFileW返回ERROR_ACCESS_DENIED(5),进程退出后转为成功),因此"备份文件变成可删除"就是旧进程已退出的可靠探针。新增
wait_for_previous_instance_exit():有界等待(30s / 200ms 轮询)后再拉起新版本,超时只记录并继续,避免旧进程异常不退时把更新卡在替换之后;首次安装(无备份)
不等待。
"仍被占用"按原始错误码判定(
file_is_still_in_use()),不能用ErrorKind:std 把 5 映射成
PermissionDenied,却把 32 映射成Uncategorized,按 kind 判会漏掉ERROR_SHARING_VIOLATION。这与单实例模块按原始码分类的理由一致。这条链路是单实例门禁收紧后才暴露的:在此之前转发恒失败、门禁放行,新版本反而
"能起来",掩盖了旧实例与新版本并存的时序问题。
验证:
cargo test -p main --bin navop --config profile.dev.package.main.debug=0:585 passed /1 failed,测试本体 3.33s。唯一失败是既有的
home_tab::tests::rendering::recent_section_does_not_participate_in_search(纯源文本契约,输入
main/src/home_tab/content.rs未被本提交触碰;它 split 的函数实际在home_layout.rs,上游已改断言),本分支基点即为红,与本改动无关。本提交新增的
restart_waits_until_the_previous_instance_releases_the_executable通过。.workbuddy/tmp/share-mode-probe/(std-only 独立 crate,与产品逻辑同构,秒级编译):3/3 PASS —— 备份被占用时消耗完整个 timeout 且不删除(410ms/400ms)、
释放后 819µs 内放行并清理备份、无旧文件时不等待(132µs);并复现 std 的错误码映射
raw 5 -> PermissionDenied/raw 32 -> Uncategorized(按ErrorKind判会漏掉 32)。.workbuddy/tmp/update_race_probe_raw.py(裸MoveFileW/DeleteFileW):运行中的 exe 可重命名(winerror=0)、不可删除(
ERROR_ACCESS_DENIED=5)、进程退出后删除成功。无法在不干扰用户真实安装的前提下构造)。