Skip to content

Commit

Permalink
Cleanup on SIGTERM and clean exit
Browse files Browse the repository at this point in the history
Note that this not solve cleanup any resources when a SIGKILL is send to the fpx-app process
  • Loading branch information
hatchan committed Oct 7, 2024
1 parent 2e7b869 commit b912fab
Show file tree
Hide file tree
Showing 5 changed files with 61 additions and 43 deletions.
3 changes: 3 additions & 0 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

7 changes: 4 additions & 3 deletions fpx-app/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -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.0-rc.14", features = ["config-toml"] }
tauri = { version = "2.0.0-rc.14", features = ["config-toml", "tracing"] }
tauri-plugin-dialog = "2.0.0-rc"
tauri-plugin-store = { version = "2.0.0-rc" }
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"] }
Expand Down
7 changes: 4 additions & 3 deletions fpx-app/src/api_manager/fpx_api.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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<Option<tokio::sync::oneshot::Sender<()>>>,
shutdown_tx: Arc<Mutex<Option<oneshot::Sender<()>>>>,
}

impl ApiManager {
Expand All @@ -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 {
Expand Down
41 changes: 18 additions & 23 deletions fpx-app/src/api_manager/legacy.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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<Option<Pid>>,
api_pid: Arc<Mutex<Option<Pid>>>,
}

impl ApiManager {
Expand All @@ -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
Expand Down Expand Up @@ -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"
);
}
}
46 changes: 32 additions & 14 deletions fpx-app/src/main.rs
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,8 @@ use tauri::menu::{MenuBuilder, MenuId, MenuItemBuilder, SubmenuBuilder};
use tauri::{Emitter, WebviewWindowBuilder};
use tauri::{Manager, Wry};
use tauri_plugin_store::StoreCollection;
use tokio::signal::unix::SignalKind;
use tracing::debug;
use tracing_subscriber::layer::SubscriberExt;
use tracing_subscriber::util::SubscriberInitExt;
use tracing_subscriber::{EnvFilter, Registry};
Expand All @@ -27,12 +29,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| {
app.handle()
.try_state::<StoreCollection<Wry>>()
Expand Down Expand Up @@ -99,17 +121,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::<AppState>();
if app_state.get_workspace().is_some() {
api.prevent_close();
window_.emit("request-close-workspace", "").unwrap();
}
}
});

Ok(())
})
.invoke_handler(tauri::generate_handler![
Expand All @@ -118,8 +129,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::<ApiManager>();
api_manager.stop_api();
};
});
}

fn setup_tracing() -> Result<()> {
Expand Down

0 comments on commit b912fab

Please sign in to comment.