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
This commit is contained in:
Federico Terzi 2022-07-03 14:03:31 +02:00 committed by GitHub
parent abf31616c3
commit 4d0cc7a6f1
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
6 changed files with 124 additions and 9 deletions

44
Cargo.lock generated
View File

@ -624,6 +624,7 @@ dependencies = [
"serde_json", "serde_json",
"serde_yaml", "serde_yaml",
"simplelog", "simplelog",
"sysinfo",
"tempdir", "tempdir",
"thiserror", "thiserror",
"widestring", "widestring",
@ -1395,9 +1396,9 @@ checksum = "830d08ce1d1d941e6b30645f1a0eb5643013d835ce3779a5fc208261dbe10f55"
[[package]] [[package]]
name = "libc" name = "libc"
version = "0.2.101" version = "0.2.126"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "3cb00336871be5ed2c8ed44b60ae9959dc5b9f08539422ed43f09e34ecaeba21" checksum = "349d5a591cd28b49e1d1037471617a32ddcda5731b99419008085f72d5a53836"
[[package]] [[package]]
name = "libdbus-sys" name = "libdbus-sys"
@ -2191,6 +2192,30 @@ dependencies = [
"rand_core 0.5.1", "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]] [[package]]
name = "rdrand" name = "rdrand"
version = "0.4.0" version = "0.4.0"
@ -2607,6 +2632,21 @@ dependencies = [
"winapi 0.3.9", "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]] [[package]]
name = "tempdir" name = "tempdir"
version = "0.3.7" version = "0.3.7"

View File

@ -56,6 +56,7 @@ colored = "2.0.0"
tempdir = "0.3.7" tempdir = "0.3.7"
notify = "4.0.17" notify = "4.0.17"
opener = "0.5.0" opener = "0.5.0"
sysinfo = "0.24.5"
[target.'cfg(windows)'.dependencies] [target.'cfg(windows)'.dependencies]
named_pipe = "0.4.1" named_pipe = "0.4.1"

View File

@ -152,6 +152,14 @@ fn start_main(paths: &Paths, _paths_overrides: &PathsOverrides, args: &ArgMatche
} }
error_eprintln!("unable to start service: timed out"); 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 SERVICE_TIMED_OUT
} }

View File

@ -19,14 +19,18 @@
use anyhow::{Error, Result}; use anyhow::{Error, Result};
use log::error; use log::error;
use std::process::Command;
use std::{path::Path, time::Instant}; use std::{path::Path, time::Instant};
use sysinfo::{PidExt, ProcessExt, ProcessRefreshKind, RefreshKind, System, SystemExt};
use thiserror::Error; use thiserror::Error;
use espanso_ipc::IPCClient; use espanso_ipc::IPCClient;
use crate::info_println;
use crate::{ use crate::{
ipc::{create_ipc_client_to_worker, IPCEvent}, ipc::{create_ipc_client_to_worker, IPCEvent},
lock::acquire_worker_lock, lock::acquire_worker_lock,
warn_eprintln,
}; };
pub fn terminate_worker(runtime_dir: &Path) -> Result<()> { pub fn terminate_worker(runtime_dir: &Path) -> Result<()> {
@ -46,14 +50,18 @@ pub fn terminate_worker(runtime_dir: &Path) -> Result<()> {
} }
} }
let now = Instant::now(); if wait_for_worker_to_be_stopped(runtime_dir) {
while now.elapsed() < std::time::Duration::from_secs(3) { return Ok(());
let lock_file = acquire_worker_lock(runtime_dir); }
if lock_file.is_some() {
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()) Err(StopError::WorkerTimedOut.into())
@ -67,3 +75,47 @@ pub enum StopError {
#[error("ipc error: `{0}`")] #[error("ipc error: `{0}`")]
IPCError(Error), 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();
}
}
}
}

View File

@ -36,6 +36,7 @@ use simplelog::{
use crate::{ use crate::{
cli::{LogMode, PathsOverrides}, cli::{LogMode, PathsOverrides},
config::load_config, config::load_config,
util::log_system_info,
}; };
mod capabilities; mod capabilities;
@ -586,6 +587,7 @@ For example, specifying 'email' is equivalent to 'match/email.yml'."#))
info!("reading configs from: {:?}", paths.config); info!("reading configs from: {:?}", paths.config);
info!("reading packages from: {:?}", paths.packages); info!("reading packages from: {:?}", paths.packages);
info!("using runtime dir: {:?}", paths.runtime); info!("using runtime dir: {:?}", paths.runtime);
log_system_info();
if handler.requires_config { if handler.requires_config {
let config_result = let config_result =

View File

@ -17,7 +17,9 @@
* along with espanso. If not, see <https://www.gnu.org/licenses/>. * along with espanso. If not, see <https://www.gnu.org/licenses/>.
*/ */
use log::info;
use std::process::Command; use std::process::Command;
use sysinfo::{System, SystemExt};
#[cfg(target_os = "windows")] #[cfg(target_os = "windows")]
pub fn set_command_flags(command: &mut Command) { pub fn set_command_flags(command: &mut Command) {
@ -43,3 +45,13 @@ pub fn attach_console() {
pub fn attach_console() { pub fn attach_console() {
// Not necessary on Linux and macOS // 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()
);
}