diff --git a/rust/Cargo.lock b/rust/Cargo.lock index dff687713..42034e1d9 100644 --- a/rust/Cargo.lock +++ b/rust/Cargo.lock @@ -777,9 +777,9 @@ dependencies = [ [[package]] name = "cargo_toml" -version = "0.21.0" +version = "0.22.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5fbd1fe9db3ebf71b89060adaf7b0504c2d6a425cf061313099547e382c2e472" +checksum = "02260d489095346e5cafd04dea8e8cb54d1d74fcd759022a9b72986ebe9a1257" dependencies = [ "serde", "toml", @@ -6504,9 +6504,9 @@ checksum = "61c41af27dd6d1e27b1b16b489db798443478cef1f06a660c96db617ba5de3b1" [[package]] name = "tauri" -version = "2.3.1" +version = "2.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3be747b26bf28674977fac47bdf6963fd9c7578271c3fbeb25d8686de6596f35" +checksum = "511dd38065a5d3b36c33cdba4362b99a40a5103bebcd4aebb930717e7c8ba292" dependencies = [ "anyhow", "bytes", @@ -6555,9 +6555,9 @@ dependencies = [ [[package]] name = "tauri-build" -version = "2.0.6" +version = "2.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "51a2e96f3c0baa0581656bb58e6fdd0f7c9c31eaf6721a0c08689d938fe85f2d" +checksum = "7ffa8732a66f90903f5a585215f3cf1e87988d0359bc88c18a502efe7572c1de" dependencies = [ "anyhow", "cargo_toml", @@ -6577,9 +6577,9 @@ dependencies = [ [[package]] name = "tauri-codegen" -version = "2.0.5" +version = "2.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e357ec3daf8faad1029bc7109e7f5b308ceb63b6073d110d7388923a4cce5e55" +checksum = "c266a247f14d63f40c6282c2653a8bac5cc3d482ca562a003a88513653ea817a" dependencies = [ "base64 0.22.1", "brotli", @@ -6604,9 +6604,9 @@ dependencies = [ [[package]] name = "tauri-macros" -version = "2.0.5" +version = "2.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "447ee4dd94690d77f1422f2b57e783c654ba75c535ad6f6e727887330804fff2" +checksum = "f47a1cf94b3bd6c4dc37dce1a43fc96120ff29a91757f0ab3cf713c7ad846e7c" dependencies = [ "heck 0.5.0", "proc-macro2", @@ -6618,9 +6618,9 @@ dependencies = [ [[package]] name = "tauri-plugin" -version = "2.0.1" +version = "2.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b2e6660a409963e4d57b9bfab4addd141eeff41bd3a7fb14e13004a832cf7ef6" +checksum = "9972871fcbddf16618f70412d965d4d845cd4b76d03fff168709961ef71e5cdf" dependencies = [ "anyhow", "glob", @@ -6738,10 +6738,11 @@ dependencies = [ [[package]] name = "tauri-runtime" -version = "2.4.0" +version = "2.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e758a405ab39e25f4d1235c5f06fe563f44b01ee18bbe38ddec5356d4f581908" +checksum = "9e9c7bce5153f1ca7bc45eba37349b31ba50e975e28edc8b5766c5ec02b0b63a" dependencies = [ + "cookie", "dpi", "gtk", "http", @@ -6757,9 +6758,9 @@ dependencies = [ [[package]] name = "tauri-runtime-wry" -version = "2.4.1" +version = "2.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8b2beb90decade4c71e8b09c9e4a9245837a8a97693f945b77e32baf13f51fec" +checksum = "087188020fd6facb8578fe9b38e81fa0fe5fb85744c73da51a299f94a530a1e3" dependencies = [ "gtk", "http", @@ -6784,10 +6785,11 @@ dependencies = [ [[package]] name = "tauri-utils" -version = "2.2.0" +version = "2.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "107a959dbd5ff53d89a98f6f2e3e987c611334141a43630caae1d80e79446dd6" +checksum = "82dcced4014e59af9790cc22f5d271df3be09ecd6728ec68861642553c8d01b7" dependencies = [ + "anyhow", "brotli", "cargo_metadata", "ctor", diff --git a/rust/Cargo.toml b/rust/Cargo.toml index 01088d44f..74453f6a7 100644 --- a/rust/Cargo.toml +++ b/rust/Cargo.toml @@ -139,13 +139,13 @@ subtle = "2.5.0" supports-color = "3.0.2" swift-bridge = "0.1.57" swift-bridge-build = "0.1.57" -tauri = "2.3.1" -tauri-build = "2.0.6" +tauri = "2.4.0" +tauri-build = "2.1.0" tauri-plugin-dialog = "2.2.0" tauri-plugin-notification = "2.2.2" tauri-plugin-opener = "2.2.6" tauri-plugin-shell = "2.2.0" -tauri-runtime = "2.4.0" +tauri-runtime = "2.5.0" tauri-utils = "2.2.0" tempfile = "3.13.0" test-case = "3.3.1" diff --git a/rust/gui-client/src-tauri/src/client.rs b/rust/gui-client/src-tauri/src/client.rs index b435bff90..df645b269 100644 --- a/rust/gui-client/src-tauri/src/client.rs +++ b/rust/gui-client/src-tauri/src/client.rs @@ -1,7 +1,7 @@ use anyhow::{Context as _, Result, bail}; use clap::{Args, Parser}; use firezone_gui_client_common::{ - self as common, controller::Failure, deep_link, settings::AdvancedSettings, + self as common, controller::Failure, deep_link, errors, settings::AdvancedSettings, }; use firezone_telemetry::Telemetry; use tracing::instrument; @@ -129,6 +129,13 @@ fn run_gui(cli: Cli) -> Result<()> { return Err(anyhow); } + // TODO: Get rid of `errors::Error` and check for sources individually like above. + if let Some(error) = anyhow.root_cause().downcast_ref::() { + common::errors::show_error_dialog(error.user_friendly_msg())?; + tracing::error!("GUI failed: {anyhow:#}"); + return Err(anyhow); + } + common::errors::show_error_dialog(common::errors::GENERIC_MSG.to_owned())?; tracing::error!("GUI failed: {anyhow:#}"); diff --git a/rust/gui-client/src-tauri/src/client/gui.rs b/rust/gui-client/src-tauri/src/client/gui.rs index b5a7752f6..74ad186f8 100644 --- a/rust/gui-client/src-tauri/src/client/gui.rs +++ b/rust/gui-client/src-tauri/src/client/gui.rs @@ -12,15 +12,14 @@ use common::system_tray::Event as TrayMenuEvent; use firezone_gui_client_common::{ self as common, controller::{Controller, ControllerRequest, CtlrTx, GuiIntegration}, - deep_link, errors, + deep_link, settings::AdvancedSettings, updates, }; use firezone_logging::err_with_src; use firezone_telemetry as telemetry; -use futures::FutureExt; use secrecy::{ExposeSecret as _, SecretString}; -use std::{panic::AssertUnwindSafe, str::FromStr, time::Duration}; +use std::{str::FromStr, time::Duration}; use tauri::Manager; use tokio::sync::mpsc; use tracing::instrument; @@ -57,6 +56,14 @@ struct TauriIntegration { tray: system_tray::Tray, } +impl Drop for TauriIntegration { + fn drop(&mut self) { + tracing::debug!("Instructing Tauri to exit"); + + self.app.exit(0); + } +} + impl GuiIntegration for TauriIntegration { fn set_welcome_window_visible(&self, visible: bool) -> Result<()> { let win = self @@ -139,6 +146,8 @@ pub(crate) fn run( inject_faults: cli.inject_faults, }; + let (handle_tx, handle_rx) = tokio::sync::oneshot::channel(); + let app = tauri::Builder::default() .manage(managed) .on_window_event(|window, event| { @@ -233,70 +242,54 @@ pub(crate) fn run( })?; let integration = TauriIntegration { app: app.handle().clone(), tray }; - let app_handle = app.handle().clone(); - let _ctlr_task = tokio::spawn(async move { - let result = AssertUnwindSafe(Controller::start( - ctlr_tx, - integration, - ctlr_rx, - advanced_settings, - reloader, - updates_rx, - )).catch_unwind().await; + // Spawn the controller + let ctrl_task = tokio::spawn(Controller::start( + ctlr_tx, + integration, + ctlr_rx, + advanced_settings, + reloader, + updates_rx, + )); - // See - // This should be the ONLY place we call `app.exit` or `app_handle.exit`, - // because it exits the entire process without dropping anything. - // - // This seems to be a platform limitation that Tauri is unable to hide - // from us. It was the source of much consternation at time of writing. - - let exit_code = match result { - Err(_panic) => { - // The panic will have been recorded already by Sentry's panic hook. - telemetry.stop_on_crash().await; - - 1 - } - Ok(Err(error)) => { - tracing::error!("run_controller returned an error: {}", err_with_src(&error)); - if let Err(e) = errors::show_error_dialog(error.user_friendly_msg()) { - tracing::error!("Failed to show error dialog: {e:#}"); - } - telemetry.stop_on_crash().await; - 1 - } - Ok(Ok(_)) => { - telemetry.stop().await; - - 0 - } - }; - - // But due to a limit in `tao` we cannot return from the event loop and must call `std::process::exit` (or Tauri's wrapper), so we explicitly flush here. - // TODO: This limit may not exist in Tauri v2 - - tracing::info!(%exit_code, "Goodbye!"); - app_handle.exit(exit_code); - // In Tauri v1, calling `App::exit` internally exited the process. - // In Tauri v2, that doesn't happen, but `App::run` still doesn't return, so we have to bail out of the process manually. - std::process::exit(exit_code); - }); + // Send the handle to the controller task back to the main thread. + // If the receiver is gone, we don't care. + let _ = handle_tx.send(ctrl_task); Ok(()) }) .build(tauri::generate_context!()) .context("Failed to build Tauri app instance")?; - app.run(|_app_handle, event| { - if let tauri::RunEvent::ExitRequested { api, .. } = event { + // Run the Tauri app to completion, i.e. until `app_handle.exit(0)` is called. + app.run_return(|_app_handle, event| { + if let tauri::RunEvent::ExitRequested { + api, code: None, .. // `code: None` means the user closed the last window. + } = event + { // Don't exit if we close our main window // https://tauri.app/v1/guides/features/system-tray/#preventing-the-app-from-closing - api.prevent_exit(); } }); - tracing::warn!("app.run returned, this is normally unreachable even in Tauri v2"); + + // Wait until the controller task finishes. + rt.block_on(async move { + let ctrl_task = handle_rx.await.context("Failed to complete setup hook")?; + let ctrl_or_timeout = tokio::time::timeout(Duration::from_secs(5), ctrl_task); + + ctrl_or_timeout + .await + .context("Controller failed to exit within 5s after OS-eventloop finished")? + .context("Controller panicked")? + .context("Controller failed")?; + + anyhow::Ok(()) + }) + .inspect_err(|_| rt.block_on(telemetry.stop_on_crash()))?; + + tracing::info!("Controller exited gracefully"); + Ok(()) }