From d858d5c407aa8e2e17dd723032ea39dc9b364996 Mon Sep 17 00:00:00 2001 From: Michael Libio Date: Mon, 23 Feb 2026 12:28:33 +0100 Subject: [PATCH 1/2] Fix: Graceful GPU shutdown before app restart (closes #896) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 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 --- .../src-tauri/src/commands/settings.rs | 25 ++++++++++++++++++- packages/desktop-app/src-tauri/src/lib.rs | 2 +- 2 files changed, 25 insertions(+), 2 deletions(-) diff --git a/packages/desktop-app/src-tauri/src/commands/settings.rs b/packages/desktop-app/src-tauri/src/commands/settings.rs index 33bb79db0..105d7f5ef 100644 --- a/packages/desktop-app/src-tauri/src/commands/settings.rs +++ b/packages/desktop-app/src-tauri/src/commands/settings.rs @@ -121,9 +121,32 @@ pub async fn select_new_database(app: tauri::AppHandle) -> Result() { + tracing::info!("Restart requested: signaling background tasks to shut down..."); + shutdown_token.cancel(); + } + + // Step 2: Brief pause to let background tasks exit their loops + // before releasing GPU resources they may still reference + std::thread::sleep(std::time::Duration::from_millis(50)); + + // Step 3: Release Metal/GPU resources to prevent SIGABRT on exit + tracing::info!("Releasing GPU resources before restart..."); + crate::release_gpu_resources(&app); + + // Step 4: Now safe to restart (exit + relaunch) + tracing::info!("GPU resources released, restarting app..."); app.restart(); } diff --git a/packages/desktop-app/src-tauri/src/lib.rs b/packages/desktop-app/src-tauri/src/lib.rs index 36bbc03c3..0805d7da2 100644 --- a/packages/desktop-app/src-tauri/src/lib.rs +++ b/packages/desktop-app/src-tauri/src/lib.rs @@ -468,7 +468,7 @@ pub fn run() { /// /// This prevents __cxa_finalize_ranges from encountering Metal resources during /// static destruction, which would cause SIGABRT in production builds. -fn release_gpu_resources(app_handle: &tauri::AppHandle) { +pub(crate) fn release_gpu_resources(app_handle: &tauri::AppHandle) { use tauri::Manager; if let Some(embedding_state) = From aaddf68e3cf7aaa3571df2fa9074e6f4f6a57513 Mon Sep 17 00:00:00 2001 From: Michael Libio Date: Mon, 23 Feb 2026 12:34:28 +0100 Subject: [PATCH 2/2] Address review: Extract graceful_shutdown helper, remove redundant import MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 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 --- .../src-tauri/src/commands/settings.rs | 21 ++--------- packages/desktop-app/src-tauri/src/lib.rs | 35 ++++++++++++------- 2 files changed, 25 insertions(+), 31 deletions(-) diff --git a/packages/desktop-app/src-tauri/src/commands/settings.rs b/packages/desktop-app/src-tauri/src/commands/settings.rs index 105d7f5ef..48d4c78fe 100644 --- a/packages/desktop-app/src-tauri/src/commands/settings.rs +++ b/packages/desktop-app/src-tauri/src/commands/settings.rs @@ -129,24 +129,9 @@ pub async fn select_new_database(app: tauri::AppHandle) -> Result() { - tracing::info!("Restart requested: signaling background tasks to shut down..."); - shutdown_token.cancel(); - } - - // Step 2: Brief pause to let background tasks exit their loops - // before releasing GPU resources they may still reference - std::thread::sleep(std::time::Duration::from_millis(50)); - - // Step 3: Release Metal/GPU resources to prevent SIGABRT on exit - tracing::info!("Releasing GPU resources before restart..."); - crate::release_gpu_resources(&app); - - // Step 4: Now safe to restart (exit + relaunch) - tracing::info!("GPU resources released, restarting app..."); + tracing::info!("Restart requested, performing graceful shutdown..."); + crate::graceful_shutdown(&app); + tracing::info!("Graceful shutdown complete, restarting app..."); app.restart(); } diff --git a/packages/desktop-app/src-tauri/src/lib.rs b/packages/desktop-app/src-tauri/src/lib.rs index 0805d7da2..c04f5ce0f 100644 --- a/packages/desktop-app/src-tauri/src/lib.rs +++ b/packages/desktop-app/src-tauri/src/lib.rs @@ -424,27 +424,20 @@ pub fn run() { event: tauri::WindowEvent::CloseRequested { .. }, .. } => { - // Window close requested - signal background tasks and release GPU resources - // This is the most reliable place to do cleanup on macOS + // Window close requested - most reliable cleanup point on macOS tracing::info!( - "Window '{}' close requested, signaling shutdown and releasing GPU context...", + "Window '{}' close requested, performing graceful shutdown...", label ); - shutdown_token_for_events.cancel(); - // Brief pause to let background tasks exit their loops before - // releasing GPU resources they may still reference - std::thread::sleep(std::time::Duration::from_millis(50)); - release_gpu_resources(app_handle); + graceful_shutdown(app_handle); } RunEvent::ExitRequested { code, .. } => { - // App exit requested - this may not fire on macOS (Tauri issue #9198) + // App exit requested - may not fire on macOS (Tauri issue #9198) tracing::info!( - "App exit requested (code: {:?}), performing cleanup...", + "App exit requested (code: {:?}), performing graceful shutdown...", code ); - shutdown_token_for_events.cancel(); - release_gpu_resources(app_handle); - tracing::info!("Cleanup complete, exiting..."); + graceful_shutdown(app_handle); } RunEvent::Exit => { // Final exit - ensure shutdown signal is sent (idempotent) @@ -456,6 +449,22 @@ pub fn run() { }); } +/// Perform graceful shutdown: cancel background tasks, wait for them to exit, then release GPU. +/// +/// This is the canonical shutdown sequence used before any process exit (quit, restart, etc.). +/// The 50ms pause lets background tasks (MCP server, domain event forwarder) exit their loops +/// before we release GPU resources they may still reference. +pub(crate) fn graceful_shutdown(app_handle: &tauri::AppHandle) { + use tauri::Manager; + + if let Some(shutdown_token) = app_handle.try_state::() { + shutdown_token.cancel(); + } + // Brief pause to let background tasks exit their loops + std::thread::sleep(std::time::Duration::from_millis(50)); + release_gpu_resources(app_handle); +} + /// Release GPU resources (Metal context and backend) to prevent SIGABRT crash on exit. /// /// This must be called before the app exits to properly clean up Metal/GPU