Skip to content

fix(desktop): fix app freeze when installing auto-update on macOS#3138

Merged
Him188 merged 1 commit into
mainfrom
fix/macos-update-exit-deadlock
Jul 10, 2026
Merged

fix(desktop): fix app freeze when installing auto-update on macOS#3138
Him188 merged 1 commit into
mainfrom
fix/macos-update-exit-deadlock

Conversation

@Him188

@Him188 Him188 commented Jul 10, 2026

Copy link
Copy Markdown
Member

问题

macOS 上点击自动更新的"安装"按钮后,app 不再自动关闭,而是直接冻结,更新无法完成。Windows 理论上也受影响。

根因

install() 由 Compose 点击回调调用,运行在 AWT EDT 上,并直接调用了 exitProcess(0):

  1. System.exit 会阻塞调用线程(EDT),等待所有 JVM shutdown hook 完成;
  2. [codex] fix JCEF browser lifecycle cleanup #3067 把 JCEF 清理的 shutdown hook 从非阻塞的 runOnCefContext(invokeLater)改成了 disposeAppBlockingblockOnCefContextSwingUtilities.invokeAndWait,即阻塞等待 EDT 来执行 CefApp dispose;
  3. EDT 等 hook,hook 等 EDT → 死锁,进程永远退不出去;
  4. 外部更新脚本 while kill -0 $OLD_PID 一直等旧进程退出,更新永远不会安装。

正常关窗/托盘退出不受影响,因为那条路径先在 EDT 内联执行了 AniCefApp.disposeBlocking(),hook 里 disposedApps 去重后变 no-op;只有更新安装路径绕过了它直接 exitProcess。JCEF 在启动时必然初始化,hook 必然注册,所以所有 macOS 用户点更新 100% 冻结。

修复(双保险)

  1. DesktopUpdateInstaller(mac + Windows):exitProcess(0) 挪到后台线程 exitProcessForUpdate,install() 返回 Succeed。EDT 保持空闲,shutdown 期间 CEF 能正常在 EDT 上 dispose。顺带消除了原来 Thread.sleep(1000) 卡 UI 一秒的问题。
  2. AniCefApp.disposeAppBlocking:非 EDT 调用时不再 invokeAndWait 无限等待,改为 invokeLater + CountDownLatch 5 秒有界等待,超时打 warning 后继续退出。以后即使再有代码从 EDT 直接 System.exit,最坏 5 秒后退出,不可能再永久冻结。EDT 上调用行为不变(内联执行),正常退出路径不受影响。

验证

  • :app:shared:app-platform:compileKotlinDesktop + :app:shared:compileKotlinDesktop BUILD SUCCESSFUL。
  • 用最小 AWT 复现程序实测(Swing 窗口 + shutdown hook):
    • 旧代码(invokeAndWait hook + EDT 上 exit)→ 永久冻结,复现线上问题;
    • 修复后更新流程(后台线程 exit + 有界 hook)→ CEF 在 EDT 干净 dispose,~1.7s 正常退出;
    • 最坏情况(仍在 EDT 上 exit)→ 5 秒超时后正常退出。
  • 未做:真实打包 .app 的端到端自动更新验证。

🤖 Generated with Claude Code

Clicking the auto-update install button freezes the app on macOS (and
would also affect Windows) instead of exiting to let the update script
take over.

Root cause: install() runs on the AWT EDT, where it called
exitProcess(0). System.exit blocks the calling thread until all JVM
shutdown hooks finish, but since #3067 the JCEF cleanup hook uses
SwingUtilities.invokeAndWait to dispose CefApp on the EDT. The EDT
waits for the hook while the hook waits for the EDT, deadlocking
forever, so the old process never exits and the external update script
waits indefinitely.

Fix:
- Exit the process from a background thread instead of the EDT, so the
  EDT stays free to process the CEF disposal during shutdown. Also
  removes the 1s UI freeze from Thread.sleep on the EDT.
- Make the JCEF shutdown hook wait for EDT disposal with a 5s bound
  instead of indefinitely, so any future System.exit from the EDT can
  no longer freeze the process.

Verified with a minimal AWT reproduction: the old code deadlocks
permanently when exiting on the EDT with a blocking hook; the fixed
flow disposes CEF cleanly and exits, and the worst case (exit on EDT)
now exits after the 5s timeout.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
@Him188 Him188 enabled auto-merge (squash) July 10, 2026 15:15
@Him188 Him188 merged commit 75aff6d into main Jul 10, 2026
22 checks passed
@Him188 Him188 deleted the fix/macos-update-exit-deadlock branch July 10, 2026 15:35
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.

1 participant