From 4d0cc7a6f1d04b32a452f3ce5cc1596f6d833538 Mon Sep 17 00:00:00 2001 From: Federico Terzi Date: Sun, 3 Jul 2022 14:03:31 +0200 Subject: [PATCH] feat(core): improve 'espanso stop' by handling non-graceful termination (#1281) * feat(core): add workaround to forcefully stop Espanso if needed. #929 * feat(core): log system info to make debugging easier --- Cargo.lock | 44 +++++++++++++++++++++- espanso/Cargo.toml | 1 + espanso/src/cli/service/mod.rs | 8 ++++ espanso/src/cli/service/stop.rs | 66 +++++++++++++++++++++++++++++---- espanso/src/main.rs | 2 + espanso/src/util.rs | 12 ++++++ 6 files changed, 124 insertions(+), 9 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index ed178f1..0a32ff4 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -624,6 +624,7 @@ dependencies = [ "serde_json", "serde_yaml", "simplelog", + "sysinfo", "tempdir", "thiserror", "widestring", @@ -1395,9 +1396,9 @@ checksum = "830d08ce1d1d941e6b30645f1a0eb5643013d835ce3779a5fc208261dbe10f55" [[package]] name = "libc" -version = "0.2.101" +version = "0.2.126" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3cb00336871be5ed2c8ed44b60ae9959dc5b9f08539422ed43f09e34ecaeba21" +checksum = "349d5a591cd28b49e1d1037471617a32ddcda5731b99419008085f72d5a53836" [[package]] name = "libdbus-sys" @@ -2191,6 +2192,30 @@ dependencies = [ "rand_core 0.5.1", ] +[[package]] +name = "rayon" +version = "1.5.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bd99e5772ead8baa5215278c9b15bf92087709e9c1b2d1f97cdb5a183c933a7d" +dependencies = [ + "autocfg", + "crossbeam-deque", + "either", + "rayon-core", +] + +[[package]] +name = "rayon-core" +version = "1.9.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "258bcdb5ac6dad48491bb2992db6b7cf74878b0384908af124823d118c99683f" +dependencies = [ + "crossbeam-channel", + "crossbeam-deque", + "crossbeam-utils", + "num_cpus", +] + [[package]] name = "rdrand" version = "0.4.0" @@ -2607,6 +2632,21 @@ dependencies = [ "winapi 0.3.9", ] +[[package]] +name = "sysinfo" +version = "0.24.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7d80929a3b477bce3a64360ca82bfb361eacce1dcb7b1fb31e8e5e181e37c212" +dependencies = [ + "cfg-if 1.0.0", + "core-foundation-sys", + "libc", + "ntapi", + "once_cell", + "rayon", + "winapi 0.3.9", +] + [[package]] name = "tempdir" version = "0.3.7" diff --git a/espanso/Cargo.toml b/espanso/Cargo.toml index 686d05b..3a8c270 100644 --- a/espanso/Cargo.toml +++ b/espanso/Cargo.toml @@ -56,6 +56,7 @@ colored = "2.0.0" tempdir = "0.3.7" notify = "4.0.17" opener = "0.5.0" +sysinfo = "0.24.5" [target.'cfg(windows)'.dependencies] named_pipe = "0.4.1" diff --git a/espanso/src/cli/service/mod.rs b/espanso/src/cli/service/mod.rs index b9dc7fc..fe6a921 100644 --- a/espanso/src/cli/service/mod.rs +++ b/espanso/src/cli/service/mod.rs @@ -152,6 +152,14 @@ fn start_main(paths: &Paths, _paths_overrides: &PathsOverrides, args: &ArgMatche } error_eprintln!("unable to start service: timed out"); + + error_eprintln!( + "Hint: sometimes this happens because another Espanso process is left running for some reason." + ); + error_eprintln!( + " Please try running 'espanso restart' or manually killing all Espanso processes, then try again." + ); + SERVICE_TIMED_OUT } diff --git a/espanso/src/cli/service/stop.rs b/espanso/src/cli/service/stop.rs index 4a39efa..9024416 100644 --- a/espanso/src/cli/service/stop.rs +++ b/espanso/src/cli/service/stop.rs @@ -19,14 +19,18 @@ use anyhow::{Error, Result}; use log::error; +use std::process::Command; use std::{path::Path, time::Instant}; +use sysinfo::{PidExt, ProcessExt, ProcessRefreshKind, RefreshKind, System, SystemExt}; use thiserror::Error; use espanso_ipc::IPCClient; +use crate::info_println; use crate::{ ipc::{create_ipc_client_to_worker, IPCEvent}, lock::acquire_worker_lock, + warn_eprintln, }; pub fn terminate_worker(runtime_dir: &Path) -> Result<()> { @@ -46,14 +50,18 @@ pub fn terminate_worker(runtime_dir: &Path) -> Result<()> { } } - let now = Instant::now(); - while now.elapsed() < std::time::Duration::from_secs(3) { - let lock_file = acquire_worker_lock(runtime_dir); - if lock_file.is_some() { - return Ok(()); - } + if wait_for_worker_to_be_stopped(runtime_dir) { + return Ok(()); + } - std::thread::sleep(std::time::Duration::from_millis(200)); + warn_eprintln!( + "unable to gracefully terminate espanso (timed-out), trying to force the termination..." + ); + + forcefully_terminate_espanso(); + + if wait_for_worker_to_be_stopped(runtime_dir) { + return Ok(()); } Err(StopError::WorkerTimedOut.into()) @@ -67,3 +75,47 @@ pub enum StopError { #[error("ipc error: `{0}`")] IPCError(Error), } + +fn wait_for_worker_to_be_stopped(runtime_dir: &Path) -> bool { + let now = Instant::now(); + while now.elapsed() < std::time::Duration::from_secs(3) { + let lock_file = acquire_worker_lock(runtime_dir); + if lock_file.is_some() { + return true; + } + + std::thread::sleep(std::time::Duration::from_millis(200)); + } + + false +} + +fn forcefully_terminate_espanso() { + let mut sys = + System::new_with_specifics(RefreshKind::new().with_processes(ProcessRefreshKind::new())); + sys.refresh_processes_specifics(ProcessRefreshKind::new()); + + let target_process_names = if cfg!(target_os = "windows") { + vec!["espanso.exe", "espansod.exe"] + } else { + vec!["espanso"] + }; + + let current_pid = std::process::id(); + + // We want to terminate all Espanso processes except this one + for (pid, process) in sys.processes() { + if target_process_names.contains(&process.name()) && pid.as_u32() != current_pid { + let str_pid = pid.as_u32().to_string(); + info_println!("killing espanso process with PID: {}", str_pid); + + if cfg!(target_os = "windows") { + let _ = Command::new("taskkill") + .args(&["/pid", &str_pid, "/f"]) + .output(); + } else { + let _ = Command::new("kill").args(&["-9", &str_pid]).output(); + } + } + } +} diff --git a/espanso/src/main.rs b/espanso/src/main.rs index 708cd8d..7db6bf2 100644 --- a/espanso/src/main.rs +++ b/espanso/src/main.rs @@ -36,6 +36,7 @@ use simplelog::{ use crate::{ cli::{LogMode, PathsOverrides}, config::load_config, + util::log_system_info, }; mod capabilities; @@ -586,6 +587,7 @@ For example, specifying 'email' is equivalent to 'match/email.yml'."#)) info!("reading configs from: {:?}", paths.config); info!("reading packages from: {:?}", paths.packages); info!("using runtime dir: {:?}", paths.runtime); + log_system_info(); if handler.requires_config { let config_result = diff --git a/espanso/src/util.rs b/espanso/src/util.rs index 1e4a22f..42602f5 100644 --- a/espanso/src/util.rs +++ b/espanso/src/util.rs @@ -17,7 +17,9 @@ * along with espanso. If not, see . */ +use log::info; use std::process::Command; +use sysinfo::{System, SystemExt}; #[cfg(target_os = "windows")] pub fn set_command_flags(command: &mut Command) { @@ -43,3 +45,13 @@ pub fn attach_console() { pub fn attach_console() { // Not necessary on Linux and macOS } + +pub fn log_system_info() { + let sys = System::new(); + info!( + "system info: {} v{} - kernel: {}", + sys.name().unwrap_or_default(), + sys.os_version().unwrap_or_default(), + sys.kernel_version().unwrap_or_default() + ); +}