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_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"

View File

@ -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"

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!(
"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
}

View File

@ -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();
}
}
}
}

View File

@ -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 =

View File

@ -17,7 +17,9 @@
* along with espanso. If not, see <https://www.gnu.org/licenses/>.
*/
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()
);
}