Fix: SIGABRT crash on app exit in production builds#885
Conversation
Three fixes to prevent SIGABRT during process shutdown: 1. Change panic = "abort" to "unwind" in release profile so panics during shutdown unwind instead of immediately aborting 2. Add graceful shutdown signaling with CancellationToken - background tasks (MCP server, domain event forwarder) now receive shutdown signals and exit their loops before the Tokio runtime drops 3. Replace OnceLock<LlamaBackend> with Mutex<Option<LlamaBackend>> so the global backend can be explicitly cleared during shutdown, preventing __cxa_finalize_ranges from destroying Metal/GPU resources during static destruction Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Pragmatic Code Review: PR #885 - Fix SIGABRT crash on app exitOverall AssessmentVerdict: Approve. This PR is a clear net positive. It addresses a real production crash (SIGABRT on macOS app exit) through three well-reasoned, complementary fixes that align precisely with the root cause analysis in issue #884. The implementation is clean, well-documented, and uses established patterns (RAII, Requirements Validation (Issue #884)
FindingsNo Critical IssuesNo blocking issues were identified. Suggested Improvements1. [Improvement]
2. [Improvement] No timeout on graceful shutdown
3. [Improvement]
NitpicksNit: Nit: Architecture AssessmentThe three-layer defense approach is well-designed:
The cleanup ordering (contexts first, then backend) is correctly enforced through the sequential calls in Security AssessmentNo security concerns. The Testing AssessmentThe PR does not add new unit tests. The nature of this fix (process shutdown behavior, GPU resource cleanup, macOS-specific crash) makes it fundamentally difficult to unit test -- the crash only manifests in release builds during Summary: This is a well-executed fix for a real production crash. The three-layer approach provides defense in depth, the code is clean and well-documented, and the architectural decisions are sound. Approve with minor suggestions noted above. |
malibio
left a comment
There was a problem hiding this comment.
Approving. This is a well-executed, three-layer fix for the SIGABRT crash on macOS app exit. The CancellationToken shutdown signaling, Mutex<Option> for explicit cleanup, and panic=unwind change are all sound and complementary. Minor improvement suggestions noted in the PR comment (shutdown timeout, pub field encapsulation) are non-blocking. Net positive change.
… redundant drop - Make ShutdownToken inner field private with cancel()/child_token() methods - Add 50ms sleep after cancel() in CloseRequested handler to let background tasks exit before GPU resource cleanup - Remove redundant explicit drop() in release_llama_backend() Addresses reviewer recommendations from PR #885 review. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Review Recommendations Addressed✅ Addressed (3)
⏭️ Skipped (2)
Summary
|
The `restart_app` command now performs the same cleanup sequence as the quit/close handlers before calling `app.restart()`: 1. Cancel ShutdownToken to signal background tasks (MCP, event forwarder) 2. Brief 50ms pause for tasks to exit their loops 3. Release Metal/GPU resources (LlamaState + LLAMA_BACKEND) 4. Then restart This prevents the SIGABRT caused by ggml_metal_rsets_free encountering active Metal residency sets during __cxa_finalize_ranges after std::process::exit(). Also addresses #885 (same crash on normal quit) since the quit path already had this fix — the restart path was simply missing it. Co-Authored-By: Claude <noreply@anthropic.com>
) * Fix: Graceful GPU shutdown before app restart (closes #896) The `restart_app` command now performs the same cleanup sequence as the quit/close handlers before calling `app.restart()`: 1. Cancel ShutdownToken to signal background tasks (MCP, event forwarder) 2. Brief 50ms pause for tasks to exit their loops 3. Release Metal/GPU resources (LlamaState + LLAMA_BACKEND) 4. Then restart This prevents the SIGABRT caused by ggml_metal_rsets_free encountering active Metal residency sets during __cxa_finalize_ranges after std::process::exit(). Also addresses #885 (same crash on normal quit) since the quit path already had this fix — the restart path was simply missing it. Co-Authored-By: Claude <noreply@anthropic.com> * Address review: Extract graceful_shutdown helper, remove redundant import - Extract `graceful_shutdown()` in lib.rs to DRY the shutdown sequence (cancel token → 50ms pause → release GPU) used by quit, close, and restart - CloseRequested and ExitRequested handlers now call graceful_shutdown() - restart_app simplified to graceful_shutdown() + app.restart() - Removed redundant `use tauri::Manager` inside restart_app (already at module scope) Addresses reviewer recommendations from PR #897 review. Co-Authored-By: Claude <noreply@anthropic.com> --------- Co-authored-by: Claude <noreply@anthropic.com>
* Fix: SIGABRT crash on app exit in production builds (closes #884) Three fixes to prevent SIGABRT during process shutdown: 1. Change panic = "abort" to "unwind" in release profile so panics during shutdown unwind instead of immediately aborting 2. Add graceful shutdown signaling with CancellationToken - background tasks (MCP server, domain event forwarder) now receive shutdown signals and exit their loops before the Tokio runtime drops 3. Replace OnceLock<LlamaBackend> with Mutex<Option<LlamaBackend>> so the global backend can be explicitly cleared during shutdown, preventing __cxa_finalize_ranges from destroying Metal/GPU resources during static destruction Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com> * Address review: encapsulate ShutdownToken, add shutdown delay, remove redundant drop - Make ShutdownToken inner field private with cancel()/child_token() methods - Add 50ms sleep after cancel() in CloseRequested handler to let background tasks exit before GPU resource cleanup - Remove redundant explicit drop() in release_llama_backend() Addresses reviewer recommendations from PR #885 review. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com> --------- Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
) * Fix: Graceful GPU shutdown before app restart (closes #896) The `restart_app` command now performs the same cleanup sequence as the quit/close handlers before calling `app.restart()`: 1. Cancel ShutdownToken to signal background tasks (MCP, event forwarder) 2. Brief 50ms pause for tasks to exit their loops 3. Release Metal/GPU resources (LlamaState + LLAMA_BACKEND) 4. Then restart This prevents the SIGABRT caused by ggml_metal_rsets_free encountering active Metal residency sets during __cxa_finalize_ranges after std::process::exit(). Also addresses #885 (same crash on normal quit) since the quit path already had this fix — the restart path was simply missing it. Co-Authored-By: Claude <noreply@anthropic.com> * Address review: Extract graceful_shutdown helper, remove redundant import - Extract `graceful_shutdown()` in lib.rs to DRY the shutdown sequence (cancel token → 50ms pause → release GPU) used by quit, close, and restart - CloseRequested and ExitRequested handlers now call graceful_shutdown() - restart_app simplified to graceful_shutdown() + app.restart() - Removed redundant `use tauri::Manager` inside restart_app (already at module scope) Addresses reviewer recommendations from PR #897 review. Co-Authored-By: Claude <noreply@anthropic.com> --------- Co-authored-by: Claude <noreply@anthropic.com>
Closes #884