From db5b7c1c3871eb56ba66505c861ea060dee0e9a4 Mon Sep 17 00:00:00 2001 From: Federico Terzi Date: Sun, 16 May 2021 18:42:56 +0200 Subject: [PATCH] feat(core): improve exit handling and connect first IPC worker POC --- Cargo.lock | 1 + espanso/Cargo.toml | 1 + espanso/src/cli/daemon/mod.rs | 22 +++++-- espanso/src/cli/worker/engine/mod.rs | 19 ++++-- espanso/src/cli/worker/engine/source/exit.rs | 54 ++++++++++++++++ espanso/src/cli/worker/engine/source/mod.rs | 1 + espanso/src/cli/worker/ipc.rs | 54 ++++++++++++++++ espanso/src/cli/worker/mod.rs | 24 +++++-- espanso/src/engine/event/mod.rs | 2 + espanso/src/engine/mod.rs | 14 ++--- espanso/src/engine/process/default.rs | 3 +- espanso/src/engine/process/middleware/exit.rs | 48 ++++++++++++++ espanso/src/engine/process/middleware/mod.rs | 1 + espanso/src/ipc.rs | 62 +++++++++++++++++++ espanso/src/main.rs | 1 + 15 files changed, 286 insertions(+), 21 deletions(-) create mode 100644 espanso/src/cli/worker/engine/source/exit.rs create mode 100644 espanso/src/cli/worker/ipc.rs create mode 100644 espanso/src/engine/process/middleware/exit.rs create mode 100644 espanso/src/ipc.rs diff --git a/Cargo.lock b/Cargo.lock index 9ab99c9..ee2e45a 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -307,6 +307,7 @@ dependencies = [ "espanso-detect", "espanso-info", "espanso-inject", + "espanso-ipc", "espanso-match", "espanso-path", "espanso-render", diff --git a/espanso/Cargo.toml b/espanso/Cargo.toml index 3850c00..2439ed6 100644 --- a/espanso/Cargo.toml +++ b/espanso/Cargo.toml @@ -23,6 +23,7 @@ espanso-clipboard = { path = "../espanso-clipboard" } espanso-info = { path = "../espanso-info" } espanso-render = { path = "../espanso-render" } espanso-path = { path = "../espanso-path" } +espanso-ipc = { path = "../espanso-ipc" } maplit = "1.0.2" simplelog = "0.9.0" log = "0.4.14" diff --git a/espanso/src/cli/daemon/mod.rs b/espanso/src/cli/daemon/mod.rs index cc83226..25fcc2d 100644 --- a/espanso/src/cli/daemon/mod.rs +++ b/espanso/src/cli/daemon/mod.rs @@ -19,9 +19,10 @@ use std::process::Command; +use espanso_ipc::IPCClient; use log::{error, info}; -use crate::lock::acquire_daemon_lock; +use crate::{ipc::{IPCEvent, create_ipc_client_to_worker}, lock::acquire_daemon_lock}; use super::{CliModule, CliModuleArgs}; @@ -53,6 +54,8 @@ fn daemon_main(args: CliModuleArgs) { info!("espanso version: {}", VERSION); // TODO: print os system and version? (with os_info crate) + let ipc_client = create_ipc_client_to_worker(&paths.runtime) + .expect("unable to create IPC client to worker process"); // TODO: check worker lock file, if taken stop the worker process through IPC @@ -60,14 +63,23 @@ fn daemon_main(args: CliModuleArgs) { let espanso_exe_path = std::env::current_exe().expect("unable to obtain espanso executable location"); - + info!("spawning the worker process..."); let mut command = Command::new(&espanso_exe_path.to_string_lossy().to_string()); command.args(&["worker"]); - command.env("ESPANSO_CONFIG_DIR", paths.config.to_string_lossy().to_string()); - command.env("ESPANSO_PACKAGE_DIR", paths.packages.to_string_lossy().to_string()); - command.env("ESPANSO_RUNTIME_DIR", paths.runtime.to_string_lossy().to_string()); + command.env( + "ESPANSO_CONFIG_DIR", + paths.config.to_string_lossy().to_string(), + ); + command.env( + "ESPANSO_PACKAGE_DIR", + paths.packages.to_string_lossy().to_string(), + ); + command.env( + "ESPANSO_RUNTIME_DIR", + paths.runtime.to_string_lossy().to_string(), + ); // On windows, we need to spawn the process as "Detached" #[cfg(target_os = "windows")] diff --git a/espanso/src/cli/worker/engine/mod.rs b/espanso/src/cli/worker/engine/mod.rs index 306c339..d2f39cf 100644 --- a/espanso/src/cli/worker/engine/mod.rs +++ b/espanso/src/cli/worker/engine/mod.rs @@ -17,9 +17,14 @@ * along with espanso. If not, see . */ +use std::thread::JoinHandle; + use anyhow::Result; +use crossbeam::channel::Receiver; use espanso_config::{config::ConfigStore, matches::store::MatchStore}; use espanso_path::Paths; +use espanso_ui::UIRemote; +use log::info; use ui::selector::MatchSelectorAdapter; use crate::cli::worker::engine::{matcher::regex::RegexMatcherAdapterOptions, path::PathProviderAdapter}; @@ -40,8 +45,10 @@ pub fn initialize_and_spawn( config_store: Box, match_store: Box, icon_paths: IconPaths, -) -> Result<()> { - std::thread::Builder::new() + ui_remote: Box, + exit_signal: Receiver<()>, +) -> Result> { + let handle = std::thread::Builder::new() .name("engine thread".to_string()) .spawn(move || { // TODO: properly order the initializations if necessary @@ -60,7 +67,8 @@ pub fn initialize_and_spawn( let (detect_source, modifier_state_store, sequencer) = super::engine::source::init_and_spawn().expect("failed to initialize detector module"); - let sources: Vec<&dyn crate::engine::funnel::Source> = vec![&detect_source]; + let exit_source = super::engine::source::exit::ExitSource::new(exit_signal, &sequencer); + let sources: Vec<&dyn crate::engine::funnel::Source> = vec![&detect_source, &exit_source]; let funnel = crate::engine::funnel::default(&sources); let rolling_matcher = super::engine::matcher::rolling::RollingMatcherAdapter::new( @@ -143,7 +151,10 @@ pub fn initialize_and_spawn( let mut engine = crate::engine::Engine::new(&funnel, &mut processor, &dispatcher); engine.run(); + + info!("engine eventloop has terminated, propagating exit event..."); + ui_remote.exit(); })?; - Ok(()) + Ok(handle) } diff --git a/espanso/src/cli/worker/engine/source/exit.rs b/espanso/src/cli/worker/engine/source/exit.rs new file mode 100644 index 0000000..3b1c351 --- /dev/null +++ b/espanso/src/cli/worker/engine/source/exit.rs @@ -0,0 +1,54 @@ +/* + * 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 crossbeam::channel::{Receiver, Select, SelectedOperation}; + +use crate::engine::{event::{Event, EventType}, funnel}; + +use super::sequencer::Sequencer; + +pub struct ExitSource<'a> { + pub exit_signal: Receiver<()>, + pub sequencer: &'a Sequencer, +} + +impl <'a> ExitSource<'a> { + pub fn new(exit_signal: Receiver<()>, sequencer: &'a Sequencer) -> Self { + ExitSource { + exit_signal, + sequencer, + } + } +} + +impl<'a> funnel::Source<'a> for ExitSource<'a> { + fn register(&'a self, select: &mut Select<'a>) -> usize { + select.recv(&self.exit_signal) + } + + fn receive(&self, op: SelectedOperation) -> Event { + op + .recv(&self.exit_signal) + .expect("unable to select data from ExitSource receiver"); + Event { + source_id: self.sequencer.next_id(), + etype: EventType::ExitRequested, + } + } +} \ No newline at end of file diff --git a/espanso/src/cli/worker/engine/source/mod.rs b/espanso/src/cli/worker/engine/source/mod.rs index a35b546..57b6be1 100644 --- a/espanso/src/cli/worker/engine/source/mod.rs +++ b/espanso/src/cli/worker/engine/source/mod.rs @@ -27,6 +27,7 @@ use detect::DetectSource; use self::{modifier::{Modifier, ModifierStateStore}, sequencer::Sequencer}; pub mod detect; +pub mod exit; pub mod modifier; pub mod sequencer; diff --git a/espanso/src/cli/worker/ipc.rs b/espanso/src/cli/worker/ipc.rs new file mode 100644 index 0000000..f2a6dc9 --- /dev/null +++ b/espanso/src/cli/worker/ipc.rs @@ -0,0 +1,54 @@ +/* + * 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, warn}; + +use crate::ipc::IPCEvent; + +pub fn initialize_and_spawn(runtime_dir: &Path, exit_notify: Sender<()>) -> Result<()> { + let receiver = crate::ipc::spawn_worker_ipc_server(runtime_dir)?; + + std::thread::Builder::new() + .name("worker-ipc-handler".to_string()) + .spawn(move || loop { + match receiver.recv() { + Ok(event) => { + match event { + IPCEvent::Exit => { + if let Err(err) = exit_notify.send(()) { + error!("experienced error while sending exit signal from worker ipc handler: {}", err); + } + }, + unexpected_event => { + warn!("received unexpected event in worker ipc handler: {:?}", unexpected_event); + }, + } + } + Err(err) => { + error!("experienced error while receiving ipc event from worker handler: {}", err); + } + } + })?; + + Ok(()) +} diff --git a/espanso/src/cli/worker/mod.rs b/espanso/src/cli/worker/mod.rs index ce39055..840005b 100644 --- a/espanso/src/cli/worker/mod.rs +++ b/espanso/src/cli/worker/mod.rs @@ -17,7 +17,8 @@ * along with espanso. If not, see . */ -use log::error; +use crossbeam::channel::unbounded; +use log::{error, info}; use crate::lock::acquire_worker_lock; @@ -27,6 +28,7 @@ use super::{CliModule, CliModuleArgs}; mod config; mod engine; +mod ipc; mod ui; pub fn new() -> CliModule { @@ -77,12 +79,26 @@ fn worker_main(args: CliModuleArgs) { .initialize() .expect("unable to initialize UI module"); - // TODO: pass the remote + let (engine_exit_notify, engine_exit_receiver) = unbounded(); + // Initialize the engine on another thread and start it - engine::initialize_and_spawn(paths.clone(), config_store, match_store, icon_paths) - .expect("unable to initialize engine"); + engine::initialize_and_spawn( + paths.clone(), + config_store, + match_store, + icon_paths, + remote, + engine_exit_receiver, + ) + .expect("unable to initialize engine"); + + // Setup the IPC server + ipc::initialize_and_spawn(&paths.runtime, engine_exit_notify) + .expect("unable to initialize IPC server"); eventloop.run(Box::new(move |event| { // TODO: handle event })); + + info!("exiting worker process..."); } diff --git a/espanso/src/engine/event/mod.rs b/espanso/src/engine/event/mod.rs index e199867..b3e3c95 100644 --- a/espanso/src/engine/event/mod.rs +++ b/espanso/src/engine/event/mod.rs @@ -47,6 +47,8 @@ impl Event { pub enum EventType { NOOP, ProcessingError(String), + ExitRequested, + Exit, // Inputs Keyboard(input::KeyboardEvent), diff --git a/espanso/src/engine/mod.rs b/espanso/src/engine/mod.rs index aae386c..26deadf 100644 --- a/espanso/src/engine/mod.rs +++ b/espanso/src/engine/mod.rs @@ -19,12 +19,7 @@ use log::{debug}; -use self::{ - dispatch::Dispatcher, - event::{Event}, - process::Processor, - funnel::{Funnel, FunnelResult}, -}; +use self::{dispatch::Dispatcher, event::{Event, EventType}, funnel::{Funnel, FunnelResult}, process::Processor}; pub mod dispatch; pub mod event; @@ -47,11 +42,16 @@ impl <'a> Engine<'a> { } pub fn run(&mut self) { - loop { + 'main: loop { match self.funnel.receive() { FunnelResult::Event(event) => { let processed_events = self.processor.process(event); for event in processed_events { + if let EventType::Exit = &event.etype { + debug!("exit event received, exiting engine"); + break 'main; + } + self.dispatcher.dispatch(event); } } diff --git a/espanso/src/engine/process/default.rs b/espanso/src/engine/process/default.rs index f8432eb..93e769e 100644 --- a/espanso/src/engine/process/default.rs +++ b/espanso/src/engine/process/default.rs @@ -25,7 +25,7 @@ use super::{MatchFilter, MatchInfoProvider, MatchSelector, Matcher, Middleware, delay_modifiers::{DelayForModifierReleaseMiddleware, ModifierStatusProvider}, markdown::MarkdownMiddleware, past_discard::PastEventsDiscardMiddleware, }}; -use crate::engine::{event::{Event, EventType}, process::middleware::image_resolve::ImageResolverMiddleware}; +use crate::engine::{event::{Event, EventType}, process::middleware::{exit::ExitMiddleware, image_resolve::ImageResolverMiddleware}}; use std::collections::VecDeque; pub struct DefaultProcessor<'a> { @@ -56,6 +56,7 @@ impl<'a> DefaultProcessor<'a> { Box::new(RenderMiddleware::new(renderer)), Box::new(ImageResolverMiddleware::new(path_provider)), Box::new(CursorHintMiddleware::new()), + Box::new(ExitMiddleware::new()), Box::new(ActionMiddleware::new(match_info_provider, event_sequence_provider)), Box::new(MarkdownMiddleware::new()), Box::new(DelayForModifierReleaseMiddleware::new(modifier_status_provider)), diff --git a/espanso/src/engine/process/middleware/exit.rs b/espanso/src/engine/process/middleware/exit.rs new file mode 100644 index 0000000..51e026b --- /dev/null +++ b/espanso/src/engine/process/middleware/exit.rs @@ -0,0 +1,48 @@ +/* + * 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 log::debug; + +use super::super::Middleware; +use crate::engine::{event::{Event, EventType}}; + +pub struct ExitMiddleware {} + +impl ExitMiddleware { + pub fn new() -> Self { + Self {} + } +} + +impl Middleware for ExitMiddleware { + fn name(&self) -> &'static str { + "exit" + } + + fn next(&self, event: Event, _: &mut dyn FnMut(Event)) -> Event { + if let EventType::ExitRequested = &event.etype { + debug!("received ExitRequested event, dispatching exit"); + return Event::caused_by(event.source_id, EventType::Exit); + } + + event + } +} + +// TODO: test diff --git a/espanso/src/engine/process/middleware/mod.rs b/espanso/src/engine/process/middleware/mod.rs index cd930b2..49076e8 100644 --- a/espanso/src/engine/process/middleware/mod.rs +++ b/espanso/src/engine/process/middleware/mod.rs @@ -21,6 +21,7 @@ pub mod action; pub mod cause; pub mod cursor_hint; pub mod delay_modifiers; +pub mod exit; pub mod image_resolve; pub mod match_select; pub mod matcher; diff --git a/espanso/src/ipc.rs b/espanso/src/ipc.rs new file mode 100644 index 0000000..0de770a --- /dev/null +++ b/espanso/src/ipc.rs @@ -0,0 +1,62 @@ +/* + * 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 anyhow::Result; +use crossbeam::channel::Receiver; +use espanso_ipc::{IPCServer, IPCClient}; +use std::path::Path; +use serde::{Serialize, Deserialize}; + +#[derive(Debug, Serialize, Deserialize)] +pub enum IPCEvent { + Exit, +} + +pub fn spawn_daemon_ipc_server(runtime_dir: &Path) -> Result> { + spawn_ipc_server(runtime_dir, "daemon") +} + +pub fn spawn_worker_ipc_server(runtime_dir: &Path) -> Result> { + spawn_ipc_server(runtime_dir, "worker") +} + +pub fn create_ipc_client_to_worker(runtime_dir: &Path) -> Result> { + create_ipc_client(runtime_dir, "worker") +} + +fn spawn_ipc_server( + runtime_dir: &Path, + name: &str, +) -> Result> { + let (server, receiver) = espanso_ipc::server(&format!("espanso{}", name), runtime_dir)?; + + std::thread::Builder::new().name(format!("espanso-ipc-server-{}", name)).spawn(move || { + server.run().expect("unable to run ipc server"); + })?; + + // TODO: refactor the ipc server to handle a graceful exit? + + Ok(receiver) +} + + +fn create_ipc_client(runtime_dir: &Path, target_process: &str) -> Result> { + let client = espanso_ipc::client(&format!("espanso{}", target_process), runtime_dir)?; + Ok(client) +} \ No newline at end of file diff --git a/espanso/src/main.rs b/espanso/src/main.rs index 2e9a25d..5eca592 100644 --- a/espanso/src/main.rs +++ b/espanso/src/main.rs @@ -35,6 +35,7 @@ use crate::cli::LogMode; mod cli; mod engine; mod gui; +mod ipc; mod lock; mod logging; mod util;