现象
macOS 上 OpenLess 通过菜单栏"退出"或 Cmd+Q 关闭时,偶尔会立刻 SIGTRAP crash。symbolicated stack trace 通常类似:
```
Application Specific Information:
Must only be used from the main thread
Thread N Crashed:
... AppKit / Carbon RemoveEventHotKey / DisposeEventHotKeyRef
... openless_lib::qa_hotkey::QaHotkeyMonitor::drop
... tauri RunEvent::Exit
```
跟之前 v1.2.9 修过的 "NSWindow 主线程要求" 是同一类问题,发生在退出时机。
根因
qa_hotkey.rs::QaHotkeyMonitor::drop(隐式或显式)会调 `global-hotkey` 的 `unregister` 路径,在 macOS 上底层是 Carbon `RemoveEventHotKey` —— 这个 API 跟 `RegisterEventHotKey` 一样严格要求主线程调用。
lib.rs:225 的 `RunEvent::Exit` callback:
```rust
RunEvent::Exit => {
coordinator.stop_qa_hotkey_listener(); // → QaHotkeyMonitor drop
}
```
RunEvent::Exit 回调在 macOS 上不保证在 AppKit 主线程跑(tauri-runtime-wry 内部线程模型决定)。如果 drop 落在非主线程,Carbon 触发 `__assert_failed` → SIGTRAP。
对比项目里其他 NSWindow / Carbon 调用都已 wrap 进 `app.run_on_main_thread`,但退出时机这条没加。
影响
- 仅 macOS
- 退出时偶发崩(用户视觉上是"OpenLess 退出时弹崩溃报告")
- 不影响数据 / 用户流程,但有崩溃报告影响信任
建议 fix
`stop_qa_hotkey_listener` 包一层 `run_on_main_thread`:
```rust
pub fn stop_qa_hotkey_listener(&self) {
let inner = Arc::clone(&self.inner);
let app = inner.app.lock().clone();
if let Some(app) = app {
let _ = app.run_on_main_thread(move || {
inner.qa_hotkey.lock().take(); // drop happens on main thread
});
} else {
// app 已 None(极端 shutdown 序),直接 drop(最坏 crash 也是退出时刻)
self.inner.qa_hotkey.lock().take();
}
}
```
注意:`run_on_main_thread` 是 fire-and-forget;退出时机如果主线程 run loop 已经 stop,回调可能不跑。退路:在 `Coordinator::new` 时就把 ns_window 的 `releasedWhenClosed: false` 设上,配合 `AppDelegate.applicationWillTerminate` 提前 unregister。这步比较深,先做最小 fix(`run_on_main_thread`)。
现象
macOS 上 OpenLess 通过菜单栏"退出"或 Cmd+Q 关闭时,偶尔会立刻 SIGTRAP crash。symbolicated stack trace 通常类似:
```
Application Specific Information:
Must only be used from the main thread
Thread N Crashed:
... AppKit / Carbon RemoveEventHotKey / DisposeEventHotKeyRef
... openless_lib::qa_hotkey::QaHotkeyMonitor::drop
... tauri RunEvent::Exit
```
跟之前 v1.2.9 修过的 "NSWindow 主线程要求" 是同一类问题,发生在退出时机。
根因
qa_hotkey.rs::QaHotkeyMonitor::drop(隐式或显式)会调 `global-hotkey` 的 `unregister` 路径,在 macOS 上底层是 Carbon `RemoveEventHotKey` —— 这个 API 跟 `RegisterEventHotKey` 一样严格要求主线程调用。lib.rs:225的 `RunEvent::Exit` callback:```rust
RunEvent::Exit => {
coordinator.stop_qa_hotkey_listener(); // → QaHotkeyMonitor drop
}
```
RunEvent::Exit回调在 macOS 上不保证在 AppKit 主线程跑(tauri-runtime-wry 内部线程模型决定)。如果 drop 落在非主线程,Carbon 触发 `__assert_failed` → SIGTRAP。对比项目里其他 NSWindow / Carbon 调用都已 wrap 进 `app.run_on_main_thread`,但退出时机这条没加。
影响
建议 fix
`stop_qa_hotkey_listener` 包一层 `run_on_main_thread`:
```rust
pub fn stop_qa_hotkey_listener(&self) {
let inner = Arc::clone(&self.inner);
let app = inner.app.lock().clone();
if let Some(app) = app {
let _ = app.run_on_main_thread(move || {
inner.qa_hotkey.lock().take(); // drop happens on main thread
});
} else {
// app 已 None(极端 shutdown 序),直接 drop(最坏 crash 也是退出时刻)
self.inner.qa_hotkey.lock().take();
}
}
```
注意:`run_on_main_thread` 是 fire-and-forget;退出时机如果主线程 run loop 已经 stop,回调可能不跑。退路:在 `Coordinator::new` 时就把 ns_window 的 `releasedWhenClosed: false` 设上,配合 `AppDelegate.applicationWillTerminate` 提前 unregister。这步比较深,先做最小 fix(`run_on_main_thread`)。