From 28d8194b4afc708c47d6f3afeaef6c93060d8cfc Mon Sep 17 00:00:00 2001 From: Federico Terzi Date: Thu, 3 Jun 2021 21:47:24 +0200 Subject: [PATCH] feat(core): implement daemon process monitor in worker --- espanso/src/cli/daemon/mod.rs | 4 +- espanso/src/cli/worker/daemon_monitor.rs | 60 ++++++++++++++++++++++++ espanso/src/cli/worker/mod.rs | 24 ++++++++-- espanso/src/main.rs | 5 +- 4 files changed, 85 insertions(+), 8 deletions(-) create mode 100644 espanso/src/cli/worker/daemon_monitor.rs diff --git a/espanso/src/cli/daemon/mod.rs b/espanso/src/cli/daemon/mod.rs index 03749d9..423bb22 100644 --- a/espanso/src/cli/daemon/mod.rs +++ b/espanso/src/cli/daemon/mod.rs @@ -95,7 +95,7 @@ fn daemon_main(args: CliModuleArgs) -> i32 { .expect("unable to initialize ipc server for daemon"); let (watcher_notify, watcher_signal) = unbounded::<()>(); - + if config_store.default().auto_restart() { watcher::initialize_and_spawn(&paths.config, watcher_notify) .expect("unable to initialize config watcher thread"); @@ -213,7 +213,7 @@ fn spawn_worker(paths: &Paths, exit_notify: Sender) { std::env::current_exe().expect("unable to obtain espanso executable location"); let mut command = Command::new(&espanso_exe_path.to_string_lossy().to_string()); - command.args(&["worker"]); + command.args(&["worker", "--monitor-daemon"]); command.env( "ESPANSO_CONFIG_DIR", paths.config.to_string_lossy().to_string(), diff --git a/espanso/src/cli/worker/daemon_monitor.rs b/espanso/src/cli/worker/daemon_monitor.rs new file mode 100644 index 0000000..da6ca9c --- /dev/null +++ b/espanso/src/cli/worker/daemon_monitor.rs @@ -0,0 +1,60 @@ +/* + * This file is part of espanso. + * + * Copyright (C) 2019-2021 Federico Terzi + * + * espanso is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * espanso is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with espanso. If not, see . + */ + +use std::{path::Path}; + +use anyhow::Result; +use crossbeam::channel::Sender; +use log::{error, info, warn}; + +use crate::lock::acquire_daemon_lock; + +const DAEMON_STATUS_CHECK_INTERVAL: u64 = 1000; + +pub fn initialize_and_spawn(runtime_dir: &Path, exit_notify: Sender<()>) -> Result<()> { + let runtime_dir_clone = runtime_dir.to_path_buf(); + std::thread::Builder::new() + .name("daemon-monitor".to_string()) + .spawn(move || { + daemon_monitor_main(&runtime_dir_clone, exit_notify.clone()); + })?; + + Ok(()) +} + +fn daemon_monitor_main(runtime_dir: &Path, exit_notify: Sender<()>) { + info!("monitoring the status of the daemon process"); + + loop { + let is_daemon_lock_free = { + let lock = acquire_daemon_lock(runtime_dir); + lock.is_some() + }; + + if is_daemon_lock_free { + warn!("detected unexpected daemon termination, sending exit signal to the engine"); + if let Err(error) = exit_notify.send(()) { + error!("unable to send daemon exit signal: {}", error); + } + break; + } + + std::thread::sleep(std::time::Duration::from_millis(DAEMON_STATUS_CHECK_INTERVAL)); + } +} diff --git a/espanso/src/cli/worker/mod.rs b/espanso/src/cli/worker/mod.rs index cd88785..bcc5914 100644 --- a/espanso/src/cli/worker/mod.rs +++ b/espanso/src/cli/worker/mod.rs @@ -20,13 +20,21 @@ use crossbeam::channel::unbounded; use log::{error, info}; -use crate::{engine::event::ExitMode, exit_code::{WORKER_ALREADY_RUNNING, WORKER_EXIT_ALL_PROCESSES, WORKER_GENERAL_ERROR, WORKER_LEGACY_ALREADY_RUNNING, WORKER_RESTART, WORKER_SUCCESS}, lock::{acquire_legacy_lock, acquire_worker_lock}}; +use crate::{ + engine::event::ExitMode, + exit_code::{ + WORKER_ALREADY_RUNNING, WORKER_EXIT_ALL_PROCESSES, WORKER_GENERAL_ERROR, + WORKER_LEGACY_ALREADY_RUNNING, WORKER_RESTART, WORKER_SUCCESS, + }, + lock::{acquire_legacy_lock, acquire_worker_lock}, +}; use self::ui::util::convert_icon_paths_to_tray_vec; use super::{CliModule, CliModuleArgs}; mod config; +mod daemon_monitor; mod engine; mod ipc; mod match_cache; @@ -47,6 +55,7 @@ pub fn new() -> CliModule { fn worker_main(args: CliModuleArgs) -> i32 { let paths = args.paths.expect("missing paths in worker main"); + let cli_args = args.cli_args.expect("missing cli_args in worker main"); // Avoid running multiple worker instances let lock_file = acquire_worker_lock(&paths.runtime); @@ -68,7 +77,7 @@ fn worker_main(args: CliModuleArgs) -> i32 { let match_store = args .match_store .expect("missing match store in worker main"); - + // TODO: show config loading errors in a GUI, if any let icon_paths = @@ -104,9 +113,18 @@ fn worker_main(args: CliModuleArgs) -> i32 { .expect("unable to initialize engine"); // Setup the IPC server - ipc::initialize_and_spawn(&paths.runtime, engine_exit_notify) + ipc::initialize_and_spawn(&paths.runtime, engine_exit_notify.clone()) .expect("unable to initialize IPC server"); + // If specified, automatically monitor the daemon status and + // terminate the worker if the daemon terminates + // This is needed to avoid "dangling" worker processes + // if the daemon crashes or is forcefully terminated. + if cli_args.is_present("monitor-daemon") { + daemon_monitor::initialize_and_spawn(&paths.runtime, engine_exit_notify.clone()) + .expect("unable to initialize daemon monitor thread"); + } + eventloop .run(Box::new(move |event| { if let Err(error) = engine_ui_event_sender.send(event) { diff --git a/espanso/src/main.rs b/espanso/src/main.rs index 41e9495..80cf0bc 100644 --- a/espanso/src/main.rs +++ b/espanso/src/main.rs @@ -273,9 +273,8 @@ fn main() { SubCommand::with_name("worker") .setting(AppSettings::Hidden) .arg( - Arg::with_name("reload") - .short("r") - .long("reload") + Arg::with_name("monitor-daemon") + .long("monitor-daemon") .required(false) .takes_value(false), ),