Skip to content

修复 Windows 托盘恢复卡死#288

Open
yananZz wants to merge 1 commit into
qxcnm:mainfrom
yananZz:fix-tray-restore-freeze
Open

修复 Windows 托盘恢复卡死#288
yananZz wants to merge 1 commit into
qxcnm:mainfrom
yananZz:fix-tray-restore-freeze

Conversation

@yananZz
Copy link
Copy Markdown

@yananZz yananZz commented May 28, 2026

变更摘要

  • 修复 Windows 桌面版关闭到托盘后,再从托盘菜单或二次启动恢复主窗口时 UI 卡死的问题。
  • Windows 关闭到托盘时改为销毁主 WebView,恢复时重新创建主窗口,避免 WebView2 隐藏恢复异常。
  • 将托盘菜单、single-instance 二次启动、前端 app_show_main_window 命令统一走异步恢复入口,避免在 single-instance 回调中同步创建/显示窗口导致回调卡住。
  • 移除二次启动时的阻塞式提示弹窗,改为日志记录。

改动范围

  • Frontend
  • Desktop / Tauri
  • Service
  • Gateway / Protocol Adapter
  • Docs / Governance
  • Workflow / Release

主要文件

  • apps/src-tauri/src/app_shell/window.rs
  • apps/src-tauri/src/app_shell/lifecycle.rs
  • apps/src-tauri/src/app_shell/tray.rs
  • apps/src-tauri/src/lib.rs
  • apps/src-tauri/src/commands/system.rs
  • apps/src-tauri/src/app_shell/mod.rs

验证

  • pnpm -C apps run test
  • pnpm -C apps run build
  • pnpm -C apps run test:ui
  • cargo test --workspace
  • 其他本地验证已说明

已执行的实际验证:

cargo test --manifest-path apps\src-tauri\Cargo.toml
结果:47 passed

pnpm -C apps run build:desktop
结果:通过

corepack pnpm dlx @tauri-apps/cli@2.10.1 build
结果:正式 NSIS/MSI 安装包生成成功

独立测试版自测:
- productName=CodexManagerTest
- identifier=com.codexmanager.desktop.test
- CODEXMANAGER_SERVICE_ADDR=localhost:48763
- 与当前已安装旧版同时运行,确认不抢占旧版 single-instance 和数据目录
- 二次启动测试版后,日志出现:
  secondary instance focus request handled without blocking dialog
  show main window requested
  show main window completed
- 未留下第二个测试进程常驻

未执行的验证与原因:

pnpm -C apps run test:本次未改前端运行时测试覆盖的 TypeScript 逻辑,已执行更贴近桌面静态导出的 pnpm -C apps run build:desktop。

pnpm -C apps run test:ui:本次改动集中在 Tauri 桌面生命周期、托盘和窗口恢复路径;该问题需要 Windows 托盘/single-instance 原生行为复现,Playwright UI 测试无法完整覆盖托盘恢复链路。

cargo test --workspace:本次改动范围仅在 apps/src-tauri 桌面壳,已执行更窄且相关的 cargo test --manifest-path apps\src-tauri\Cargo.toml。

风险与影响面

  • 影响平台:主要影响 Windows 桌面版托盘关闭与恢复路径。
  • macOS Reopen 入口改为同一异步恢复入口,行为应保持为显示主窗口。
  • 不涉及 service、gateway、协议适配、数据库迁移、环境变量或发布 workflow。
  • Windows 关闭到托盘现在强制走轻量关闭模式,会在托盘保活时销毁主 WebView;恢复时会重新创建窗口,因此恢复时页面会重新加载。
  • 已通过独立测试版验证 single-instance 恢复不再卡住,但最终仍建议用正式安装包在真实安装目录完成一次人工回归。

备注

  • 提交前已确认未包含敏感 token、cookie、API key。

@KilimiaoSix KilimiaoSix requested a review from qxcnm May 29, 2026 10:03
Copy link
Copy Markdown
Collaborator

@KilimiaoSix KilimiaoSix left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

本轮 code review 结论:暂不建议合并,建议先修复后再重新 review。

主要原因如下:

  1. 恢复失败后可能导致应用无法正常退出

    apps/src-tauri/src/app_shell/window.rs 中,KEEP_ALIVE_FOR_LIGHTWEIGHT_CLOSE 只在 window.show() 成功后才会被清除。若 lightweight close 后恢复主窗口时 ensure_main_window()window.show() 失败,该 flag 会继续保持为 true

    后续 RunEvent::ExitRequested 会因为 should_keep_alive_for_lightweight_close() 返回 true 而 prevent_exit(),可能导致应用进入无主窗口但仍无法正常退出的状态。这是我认为当前最需要先修复的阻塞问题。

  2. app_show_main_window 的成功返回语义不再可靠

    app_show_main_window 现在调用 request_show_main_window() 后立即返回 Ok(()),但实际恢复窗口的动作是在 detached thread 中 sleep 50ms 后再投递到主线程执行。

    这意味着前端拿到 Ok(()) 时,窗口可能尚未显示,甚至后续可能因为 run_on_main_thread、窗口创建、show()focus 失败而没有恢复。调用方无法感知失败,也无法重试或提示用户。

  3. 延迟恢复任务缺少退出态保护

    request_show_main_window() 延迟执行前后没有检查应用是否已经进入退出流程。若用户发起显示主窗口后立刻从托盘退出,延迟任务仍可能在退出过程中尝试创建、显示或聚焦主窗口,带来 shutdown race 和偶发异常。

  4. 每次恢复请求都会创建新的 sleeping OS thread,且没有合并重复请求

    托盘菜单、single-instance、macOS Reopen、前端命令都会走 request_show_main_window()。当前每次调用都会 std::thread::spawn 一个线程并 sleep 50ms。连续点击托盘、重复启动应用等场景会产生多个重复的恢复任务,放大窗口生命周期竞态。

  5. 移除二次启动弹窗后,恢复失败没有用户可见 fallback

    旧逻辑至少会通过弹窗提示用户应用已在运行。新逻辑只记录日志。如果异步恢复失败或 OS 拒绝前台聚焦,用户可能看到二次启动“无反应”。移除阻塞弹窗方向可以理解,但建议补充非阻塞的失败处理或更可靠的恢复结果处理。

建议修复方向:

  • 恢复流程开始或失败时明确收敛 KEEP_ALIVE_FOR_LIGHTWEIGHT_CLOSE 状态,避免失败后卡在 keep-alive。
  • request_show_main_window() 在执行前检查 APP_EXIT_REQUESTED,退出中则跳过。
  • 为恢复请求增加 pending/coalescing 机制,避免重复线程和重复 show/focus。
  • 明确 app_show_main_window 的语义:如果只是“恢复请求已提交”,前端和命名应体现这一点;如果语义是“窗口已显示”,则需要返回真实执行结果。
  • 尽量避免固定 50ms sleep 作为核心同步机制,或者至少用注释说明该延迟的必要性和边界。

因此本轮建议 Request changes,暂缓合并。

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants