Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
10 changes: 9 additions & 1 deletion packages/desktop-app/src-tauri/src/commands/settings.rs
Original file line number Diff line number Diff line change
Expand Up @@ -121,9 +121,17 @@ pub async fn select_new_database(app: tauri::AppHandle) -> Result<PendingDatabas
})
}

/// Restart the application.
/// Restart the application with graceful GPU/background task shutdown.
///
/// Without explicit cleanup, `app.restart()` calls `std::process::exit()` which
/// triggers C++ destructors via `__cxa_finalize_ranges`. The Metal residency sets
/// for the embedding model are still active, causing a SIGABRT assertion failure
/// in `ggml_metal_rsets_free`.
#[tauri::command]
pub fn restart_app(app: tauri::AppHandle) {
tracing::info!("Restart requested, performing graceful shutdown...");
crate::graceful_shutdown(&app);
tracing::info!("Graceful shutdown complete, restarting app...");
app.restart();
}

Expand Down
37 changes: 23 additions & 14 deletions packages/desktop-app/src-tauri/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand All @@ -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::<ShutdownToken>() {
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
Expand All @@ -468,7 +477,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) =
Expand Down