From 32a23729b134f17e50dca2d7d8e2c89c955c72e9 Mon Sep 17 00:00:00 2001 From: Benno van den Berg Date: Mon, 7 Oct 2024 16:34:39 +0200 Subject: [PATCH] Cleanup on SIGTERM and clean exit Note that this not solve cleanup any resources when a SIGKILL is send to the fpx-app process --- Cargo.lock | 3 ++ fpx-app/Cargo.toml | 7 +++-- fpx-app/src/api_manager/fpx_api.rs | 7 +++-- fpx-app/src/api_manager/legacy.rs | 41 ++++++++++++-------------- fpx-app/src/main.rs | 47 +++++++++++++++++++++--------- 5 files changed, 62 insertions(+), 43 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 139cb0f85..5e378fbfc 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -4919,6 +4919,7 @@ dependencies = [ "tauri-utils", "thiserror", "tokio", + "tracing", "tray-icon", "url", "urlpattern", @@ -5116,6 +5117,7 @@ dependencies = [ "tao", "tauri-runtime", "tauri-utils", + "tracing", "url", "webkit2gtk", "webview2-com", @@ -6761,6 +6763,7 @@ dependencies = [ "soup3", "tao-macros", "thiserror", + "tracing", "webkit2gtk", "webkit2gtk-sys", "webview2-com", diff --git a/fpx-app/Cargo.toml b/fpx-app/Cargo.toml index 41a84abdb..719048139 100644 --- a/fpx-app/Cargo.toml +++ b/fpx-app/Cargo.toml @@ -26,13 +26,14 @@ nix = { version = "0.29", default-features = false, features = ["signal"] } schemars = { workspace = true } serde = { workspace = true } serde_json = { workspace = true } -tauri = { version = "2.0.1", features = ["config-toml"] } +tauri = { version = "2.0.1", features = ["config-toml", "tracing"] } tauri-plugin-dialog = "2.0.1" tauri-plugin-store = { version = "2.0.1" } tokio = { version = "1", default-features = false, features = [ - "sync", - "process", "macros", + "process", + "signal", + "sync", ] } tracing = { version = "0.1" } tracing-subscriber = { version = "0.3", features = ["env-filter"] } diff --git a/fpx-app/src/api_manager/fpx_api.rs b/fpx-app/src/api_manager/fpx_api.rs index 5ee8f34a7..675fbea06 100644 --- a/fpx-app/src/api_manager/fpx_api.rs +++ b/fpx-app/src/api_manager/fpx_api.rs @@ -6,12 +6,13 @@ use fpx::service::Service; use std::sync::{Arc, Mutex}; use tauri::async_runtime::spawn; use tokio::sync::broadcast::error::RecvError; +use tokio::sync::oneshot; use tracing::{error, info, trace, warn}; -#[derive(Debug, Default)] +#[derive(Debug, Default, Clone)] pub struct ApiManager { // Sending a message on this channel will shutdown the axum server. - shutdown_tx: Mutex>>, + shutdown_tx: Arc>>>, } impl ApiManager { @@ -28,7 +29,7 @@ impl ApiManager { let listener = std::net::TcpListener::bind(format!("127.0.0.1:{listen_port}")).unwrap(); listener.set_nonblocking(true).unwrap(); - let (shutdown, on_shutdown) = tokio::sync::oneshot::channel::<()>(); + let (shutdown, on_shutdown) = oneshot::channel::<()>(); *shutdown_tx = Some(shutdown); spawn(async move { diff --git a/fpx-app/src/api_manager/legacy.rs b/fpx-app/src/api_manager/legacy.rs index a3376f5a3..6c47a8f6c 100644 --- a/fpx-app/src/api_manager/legacy.rs +++ b/fpx-app/src/api_manager/legacy.rs @@ -3,13 +3,13 @@ use nix::sys::signal::{killpg, Signal}; use nix::unistd::Pid; use std::os::unix::process::CommandExt; use std::process; -use std::sync::Mutex; +use std::sync::{Arc, Mutex}; use tauri::async_runtime::spawn; use tracing::{error, trace, warn}; -#[derive(Debug, Default)] +#[derive(Debug, Default, Clone)] pub struct ApiManager { - api_pid: Mutex>, + api_pid: Arc>>, } impl ApiManager { @@ -23,7 +23,7 @@ impl ApiManager { // signal to that process group. if let Some(api_pid) = api_pid.take() { // shutdown any existing api server - Self::send_sigterm_signal(api_pid); + send_sigterm_signal(api_pid); } // Create some environment variables overrides based on the fpx.toml @@ -61,28 +61,23 @@ impl ApiManager { /// Sends the SIGTERM signal to the API process group. If no API pid was set /// then this function will do nothing. pub fn stop_api(&self) { - let Some(api_pid) = self.api_pid.lock().expect("lock is poisoned").take() else { - trace!("No API running"); - return; + match self.api_pid.lock().expect("lock is poisoned").take() { + Some(api_pid) => send_sigterm_signal(api_pid), + _ => trace!("No API running"), }; - - Self::send_sigterm_signal(api_pid) } +} - /// Send the SIGTERM signal to the specified process group. - /// - /// This uses a Process ID type instead of a specific process group ID as - /// that does not exist. - fn send_sigterm_signal(api_pid: Pid) { - trace!(?api_pid, "sending SIGTERM signal to API process group"); +/// Send the SIGTERM signal to the specified process group. +fn send_sigterm_signal(api_pid: Pid) { + trace!(?api_pid, "sending SIGTERM signal to API process group"); - let result = killpg(api_pid, Signal::SIGTERM); - if let Err(errno) = result { - warn!( - ?errno, - ?api_pid, - "failed to send SIGNTERM signal to API process group" - ); - } + let result = killpg(api_pid, Signal::SIGTERM); + if let Err(errno) = result { + warn!( + ?errno, + ?api_pid, + "failed to send SIGTERM signal to API process group" + ); } } diff --git a/fpx-app/src/main.rs b/fpx-app/src/main.rs index 1bc67c08b..5a9db5a55 100644 --- a/fpx-app/src/main.rs +++ b/fpx-app/src/main.rs @@ -9,7 +9,10 @@ use std::time::Duration; use tauri::menu::{MenuBuilder, MenuId, MenuItemBuilder, SubmenuBuilder}; use tauri::{Emitter, WebviewWindowBuilder}; use tauri::{Manager, Wry}; +use tauri_plugin_store::StoreCollection; use tauri_plugin_store::{StoreCollection, StoreExt}; +use tokio::signal::unix::SignalKind; +use tracing::debug; use tracing_subscriber::layer::SubscriberExt; use tracing_subscriber::util::SubscriberInitExt; use tracing_subscriber::{EnvFilter, Registry}; @@ -28,12 +31,32 @@ fn main() { std::process::exit(1); } + let api_manager = ApiManager::default(); + + // Create a signal handler which will cleanup any resources in use + // (currently only the api manager) when fpx-app receives the SIGTERM signal. + let api_manager_ = api_manager.clone(); + tauri::async_runtime::spawn(async move { + // Block until we receive a SIGTERM signal + tokio::signal::unix::signal(SignalKind::terminate()) + .expect("Unable to set signal handler") + .recv() + .await; + + debug!("received SIGTERM signal, stopping API server"); + + api_manager_.stop_api(); + + // Do we need to exit the process? + std::process::exit(0); + }); + tauri::Builder::default() .plugin(tauri_plugin_window_state::Builder::new().build()) .plugin(tauri_plugin_store::Builder::new().build()) .plugin(tauri_plugin_dialog::init()) .manage(AppState::default()) - .manage(ApiManager::default()) + .manage(api_manager) .setup(|app| { // Init store and load it from disk let store = app @@ -112,17 +135,6 @@ fn main() { } }); - let window_ = window.clone(); - window.on_window_event(move |event| { - if let tauri::WindowEvent::CloseRequested { api, .. } = event { - let app_state = window_.state::(); - if app_state.get_workspace().is_some() { - api.prevent_close(); - window_.emit("request-close-workspace", "").unwrap(); - } - } - }); - Ok(()) }) .invoke_handler(tauri::generate_handler![ @@ -131,8 +143,15 @@ fn main() { commands::workspace::list_recent_workspaces, commands::workspace::open_workspace_by_path, ]) - .run(tauri::generate_context!()) - .expect("error while running tauri application"); + .build(tauri::generate_context!()) + .expect("failed to build application") + .run(|app_handle, event| { + // Make sure we cleanup after the app is going to exit + if let tauri::RunEvent::ExitRequested { .. } = event { + let api_manager = app_handle.state::(); + api_manager.stop_api(); + }; + }); } fn setup_tracing() -> Result<()> {