feat(core): improve exit handling and connect first IPC worker POC

This commit is contained in:
Federico Terzi 2021-05-16 18:42:56 +02:00
parent 91046b3c18
commit db5b7c1c38
15 changed files with 286 additions and 21 deletions

1
Cargo.lock generated
View File

@ -307,6 +307,7 @@ dependencies = [
"espanso-detect",
"espanso-info",
"espanso-inject",
"espanso-ipc",
"espanso-match",
"espanso-path",
"espanso-render",

View File

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

View File

@ -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
@ -65,9 +68,18 @@ fn daemon_main(args: CliModuleArgs) {
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")]

View File

@ -17,9 +17,14 @@
* along with espanso. If not, see <https://www.gnu.org/licenses/>.
*/
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<dyn ConfigStore>,
match_store: Box<dyn MatchStore>,
icon_paths: IconPaths,
) -> Result<()> {
std::thread::Builder::new()
ui_remote: Box<dyn UIRemote>,
exit_signal: Receiver<()>,
) -> Result<JoinHandle<()>> {
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)
}

View File

@ -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 <https://www.gnu.org/licenses/>.
*/
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,
}
}
}

View File

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

View File

@ -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 <https://www.gnu.org/licenses/>.
*/
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(())
}

View File

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

View File

@ -47,6 +47,8 @@ impl Event {
pub enum EventType {
NOOP,
ProcessingError(String),
ExitRequested,
Exit,
// Inputs
Keyboard(input::KeyboardEvent),

View File

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

View File

@ -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)),

View File

@ -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 <https://www.gnu.org/licenses/>.
*/
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

View File

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

62
espanso/src/ipc.rs Normal file
View File

@ -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 <https://www.gnu.org/licenses/>.
*/
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<Receiver<IPCEvent>> {
spawn_ipc_server(runtime_dir, "daemon")
}
pub fn spawn_worker_ipc_server(runtime_dir: &Path) -> Result<Receiver<IPCEvent>> {
spawn_ipc_server(runtime_dir, "worker")
}
pub fn create_ipc_client_to_worker(runtime_dir: &Path) -> Result<impl IPCClient<IPCEvent>> {
create_ipc_client(runtime_dir, "worker")
}
fn spawn_ipc_server(
runtime_dir: &Path,
name: &str,
) -> Result<Receiver<IPCEvent>> {
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<impl IPCClient<IPCEvent>> {
let client = espanso_ipc::client(&format!("espanso{}", target_process), runtime_dir)?;
Ok(client)
}

View File

@ -35,6 +35,7 @@ use crate::cli::LogMode;
mod cli;
mod engine;
mod gui;
mod ipc;
mod lock;
mod logging;
mod util;