feat(core): improve exit handling
This commit is contained in:
parent
cba94607fa
commit
39758e2b9a
|
@ -23,9 +23,7 @@ use anyhow::Result;
|
||||||
use crossbeam::channel::{Sender};
|
use crossbeam::channel::{Sender};
|
||||||
use log::{error, warn};
|
use log::{error, warn};
|
||||||
|
|
||||||
use crate::ipc::IPCEvent;
|
use crate::{exit_code::DAEMON_SUCCESS, ipc::IPCEvent};
|
||||||
|
|
||||||
use super::ExitCode;
|
|
||||||
|
|
||||||
pub fn initialize_and_spawn(runtime_dir: &Path, exit_notify: Sender<i32>) -> Result<()> {
|
pub fn initialize_and_spawn(runtime_dir: &Path, exit_notify: Sender<i32>) -> Result<()> {
|
||||||
let receiver = crate::ipc::spawn_daemon_ipc_server(runtime_dir)?;
|
let receiver = crate::ipc::spawn_daemon_ipc_server(runtime_dir)?;
|
||||||
|
@ -37,7 +35,7 @@ pub fn initialize_and_spawn(runtime_dir: &Path, exit_notify: Sender<i32>) -> Res
|
||||||
Ok(event) => {
|
Ok(event) => {
|
||||||
match event {
|
match event {
|
||||||
IPCEvent::Exit => {
|
IPCEvent::Exit => {
|
||||||
if let Err(err) = exit_notify.send(ExitCode::Success as i32) {
|
if let Err(err) = exit_notify.send(DAEMON_SUCCESS) {
|
||||||
error!("experienced error while sending exit signal from daemon ipc handler: {}", err);
|
error!("experienced error while sending exit signal from daemon ipc handler: {}", err);
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
|
@ -27,20 +27,12 @@ use espanso_ipc::IPCClient;
|
||||||
use espanso_path::Paths;
|
use espanso_path::Paths;
|
||||||
use log::{error, info, warn};
|
use log::{error, info, warn};
|
||||||
|
|
||||||
use crate::{
|
use crate::{exit_code::{DAEMON_ALREADY_RUNNING, DAEMON_GENERAL_ERROR, DAEMON_SUCCESS, WORKER_EXIT_ALL_PROCESSES, WORKER_SUCCESS}, ipc::{create_ipc_client_to_worker, IPCEvent}, lock::{acquire_daemon_lock, acquire_worker_lock}};
|
||||||
ipc::{create_ipc_client_to_worker, IPCEvent},
|
|
||||||
lock::{acquire_daemon_lock, acquire_worker_lock},
|
|
||||||
};
|
|
||||||
|
|
||||||
use super::{CliModule, CliModuleArgs};
|
use super::{CliModule, CliModuleArgs};
|
||||||
|
|
||||||
mod ipc;
|
mod ipc;
|
||||||
|
|
||||||
pub enum ExitCode {
|
|
||||||
Success = 0,
|
|
||||||
ExitCodeUnwrapError = 100,
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn new() -> CliModule {
|
pub fn new() -> CliModule {
|
||||||
#[allow(clippy::needless_update)]
|
#[allow(clippy::needless_update)]
|
||||||
CliModule {
|
CliModule {
|
||||||
|
@ -63,7 +55,7 @@ fn daemon_main(args: CliModuleArgs) -> i32 {
|
||||||
let lock_file = acquire_daemon_lock(&paths.runtime);
|
let lock_file = acquire_daemon_lock(&paths.runtime);
|
||||||
if lock_file.is_none() {
|
if lock_file.is_none() {
|
||||||
error!("daemon is already running!");
|
error!("daemon is already running!");
|
||||||
return 1;
|
return DAEMON_ALREADY_RUNNING;
|
||||||
}
|
}
|
||||||
|
|
||||||
// TODO: we might need to check preconditions: accessibility on macOS, presence of binaries on Linux, etc
|
// TODO: we might need to check preconditions: accessibility on macOS, presence of binaries on Linux, etc
|
||||||
|
@ -87,18 +79,26 @@ fn daemon_main(args: CliModuleArgs) -> i32 {
|
||||||
|
|
||||||
// TODO: start file watcher thread
|
// TODO: start file watcher thread
|
||||||
|
|
||||||
let mut exit_code: i32 = ExitCode::Success as i32;
|
let mut exit_code: i32 = DAEMON_SUCCESS;
|
||||||
|
|
||||||
loop {
|
loop {
|
||||||
select! {
|
select! {
|
||||||
recv(exit_signal) -> code => {
|
recv(exit_signal) -> code => {
|
||||||
match code {
|
match code {
|
||||||
Ok(code) => {
|
Ok(code) => {
|
||||||
exit_code = code
|
match code {
|
||||||
|
WORKER_EXIT_ALL_PROCESSES => {
|
||||||
|
info!("worker requested a general exit, quitting the daemon");
|
||||||
|
}
|
||||||
|
_ => {
|
||||||
|
error!("received unexpected exit code from worker {}, exiting", code);
|
||||||
|
exit_code = code
|
||||||
|
}
|
||||||
|
}
|
||||||
},
|
},
|
||||||
Err(err) => {
|
Err(err) => {
|
||||||
error!("received error when unwrapping exit_code: {}", err);
|
error!("received error when unwrapping exit_code: {}", err);
|
||||||
exit_code = ExitCode::ExitCodeUnwrapError as i32;
|
exit_code = DAEMON_GENERAL_ERROR;
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
|
@ -177,11 +177,7 @@ fn spawn_worker(paths: &Paths, exit_notify: Sender<i32>) {
|
||||||
let result = child.wait();
|
let result = child.wait();
|
||||||
if let Ok(status) = result {
|
if let Ok(status) = result {
|
||||||
if let Some(code) = status.code() {
|
if let Some(code) = status.code() {
|
||||||
if code != 0 {
|
if code != WORKER_SUCCESS {
|
||||||
error!(
|
|
||||||
"worker process exited with non-zero code: {}, exiting",
|
|
||||||
code
|
|
||||||
);
|
|
||||||
exit_notify
|
exit_notify
|
||||||
.send(code)
|
.send(code)
|
||||||
.expect("unable to forward worker exit code");
|
.expect("unable to forward worker exit code");
|
||||||
|
|
|
@ -18,8 +18,7 @@
|
||||||
*/
|
*/
|
||||||
|
|
||||||
use std::{
|
use std::{
|
||||||
cell::RefCell,
|
collections::{HashSet},
|
||||||
collections::{HashMap, HashSet},
|
|
||||||
};
|
};
|
||||||
|
|
||||||
use crate::engine::{dispatch::ModeProvider, process::MatchFilter};
|
use crate::engine::{dispatch::ModeProvider, process::MatchFilter};
|
||||||
|
|
|
@ -48,7 +48,7 @@ pub fn initialize_and_spawn(
|
||||||
ui_remote: Box<dyn UIRemote>,
|
ui_remote: Box<dyn UIRemote>,
|
||||||
exit_signal: Receiver<()>,
|
exit_signal: Receiver<()>,
|
||||||
ui_event_receiver: Receiver<UIEvent>,
|
ui_event_receiver: Receiver<UIEvent>,
|
||||||
) -> Result<JoinHandle<()>> {
|
) -> Result<JoinHandle<bool>> {
|
||||||
let handle = std::thread::Builder::new()
|
let handle = std::thread::Builder::new()
|
||||||
.name("engine thread".to_string())
|
.name("engine thread".to_string())
|
||||||
.spawn(move || {
|
.spawn(move || {
|
||||||
|
@ -154,10 +154,12 @@ pub fn initialize_and_spawn(
|
||||||
);
|
);
|
||||||
|
|
||||||
let mut engine = crate::engine::Engine::new(&funnel, &mut processor, &dispatcher);
|
let mut engine = crate::engine::Engine::new(&funnel, &mut processor, &dispatcher);
|
||||||
engine.run();
|
let exit_all_processes = engine.run();
|
||||||
|
|
||||||
info!("engine eventloop has terminated, propagating exit event...");
|
info!("engine eventloop has terminated, propagating exit event...");
|
||||||
ui_remote.exit();
|
ui_remote.exit();
|
||||||
|
|
||||||
|
exit_all_processes
|
||||||
})?;
|
})?;
|
||||||
|
|
||||||
Ok(handle)
|
Ok(handle)
|
||||||
|
|
|
@ -48,7 +48,7 @@ impl<'a> funnel::Source<'a> for ExitSource<'a> {
|
||||||
.expect("unable to select data from ExitSource receiver");
|
.expect("unable to select data from ExitSource receiver");
|
||||||
Event {
|
Event {
|
||||||
source_id: self.sequencer.next_id(),
|
source_id: self.sequencer.next_id(),
|
||||||
etype: EventType::ExitRequested,
|
etype: EventType::ExitRequested(false),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
|
@ -20,7 +20,7 @@
|
||||||
use crossbeam::channel::unbounded;
|
use crossbeam::channel::unbounded;
|
||||||
use log::{error, info};
|
use log::{error, info};
|
||||||
|
|
||||||
use crate::lock::acquire_worker_lock;
|
use crate::{exit_code::{WORKER_ALREADY_RUNNING, WORKER_EXIT_ALL_PROCESSES, WORKER_GENERAL_ERROR, WORKER_SUCCESS}, lock::acquire_worker_lock};
|
||||||
|
|
||||||
use self::ui::util::convert_icon_paths_to_tray_vec;
|
use self::ui::util::convert_icon_paths_to_tray_vec;
|
||||||
|
|
||||||
|
@ -51,7 +51,7 @@ fn worker_main(args: CliModuleArgs) -> i32 {
|
||||||
let lock_file = acquire_worker_lock(&paths.runtime);
|
let lock_file = acquire_worker_lock(&paths.runtime);
|
||||||
if lock_file.is_none() {
|
if lock_file.is_none() {
|
||||||
error!("worker is already running!");
|
error!("worker is already running!");
|
||||||
return 1;
|
return WORKER_ALREADY_RUNNING;
|
||||||
}
|
}
|
||||||
|
|
||||||
let config_store = args
|
let config_store = args
|
||||||
|
@ -83,7 +83,7 @@ fn worker_main(args: CliModuleArgs) -> i32 {
|
||||||
let (engine_ui_event_sender, engine_ui_event_receiver) = unbounded();
|
let (engine_ui_event_sender, engine_ui_event_receiver) = unbounded();
|
||||||
|
|
||||||
// Initialize the engine on another thread and start it
|
// Initialize the engine on another thread and start it
|
||||||
engine::initialize_and_spawn(
|
let engine_handle = engine::initialize_and_spawn(
|
||||||
paths.clone(),
|
paths.clone(),
|
||||||
config_store,
|
config_store,
|
||||||
match_store,
|
match_store,
|
||||||
|
@ -98,13 +98,28 @@ fn worker_main(args: CliModuleArgs) -> i32 {
|
||||||
ipc::initialize_and_spawn(&paths.runtime, engine_exit_notify)
|
ipc::initialize_and_spawn(&paths.runtime, engine_exit_notify)
|
||||||
.expect("unable to initialize IPC server");
|
.expect("unable to initialize IPC server");
|
||||||
|
|
||||||
eventloop.run(Box::new(move |event| {
|
eventloop
|
||||||
if let Err(error) = engine_ui_event_sender.send(event) {
|
.run(Box::new(move |event| {
|
||||||
error!("unable to send UIEvent to engine: {}", error);
|
if let Err(error) = engine_ui_event_sender.send(event) {
|
||||||
}
|
error!("unable to send UIEvent to engine: {}", error);
|
||||||
})).expect("unable to run main eventloop");
|
}
|
||||||
|
}))
|
||||||
|
.expect("unable to run main eventloop");
|
||||||
|
|
||||||
info!("exiting worker process...");
|
info!("waiting for engine exit mode...");
|
||||||
|
match engine_handle.join() {
|
||||||
0
|
Ok(exit_all_processes) => {
|
||||||
|
if exit_all_processes {
|
||||||
|
info!("exiting worker process and daemon...");
|
||||||
|
return WORKER_EXIT_ALL_PROCESSES;
|
||||||
|
} else {
|
||||||
|
info!("exiting worker process...");
|
||||||
|
return WORKER_SUCCESS;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Err(err) => {
|
||||||
|
error!("unable to read engine exit mode: {:?}", err);
|
||||||
|
return WORKER_GENERAL_ERROR;
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -48,8 +48,8 @@ impl Event {
|
||||||
pub enum EventType {
|
pub enum EventType {
|
||||||
NOOP,
|
NOOP,
|
||||||
ProcessingError(String),
|
ProcessingError(String),
|
||||||
ExitRequested,
|
ExitRequested(bool), // If true, exit also the daemon process and not just the worker
|
||||||
Exit,
|
Exit(bool),
|
||||||
|
|
||||||
// Inputs
|
// Inputs
|
||||||
Keyboard(input::KeyboardEvent),
|
Keyboard(input::KeyboardEvent),
|
||||||
|
|
|
@ -41,15 +41,15 @@ impl <'a> Engine<'a> {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn run(&mut self) {
|
pub fn run(&mut self) -> bool {
|
||||||
'main: loop {
|
loop {
|
||||||
match self.funnel.receive() {
|
match self.funnel.receive() {
|
||||||
FunnelResult::Event(event) => {
|
FunnelResult::Event(event) => {
|
||||||
let processed_events = self.processor.process(event);
|
let processed_events = self.processor.process(event);
|
||||||
for event in processed_events {
|
for event in processed_events {
|
||||||
if let EventType::Exit = &event.etype {
|
if let EventType::Exit(exit_all_processes) = &event.etype {
|
||||||
debug!("exit event received, exiting engine");
|
debug!("exit event received, exiting engine");
|
||||||
break 'main;
|
return *exit_all_processes;
|
||||||
}
|
}
|
||||||
|
|
||||||
self.dispatcher.dispatch(event);
|
self.dispatcher.dispatch(event);
|
||||||
|
@ -57,7 +57,7 @@ impl <'a> Engine<'a> {
|
||||||
}
|
}
|
||||||
FunnelResult::EndOfStream => {
|
FunnelResult::EndOfStream => {
|
||||||
debug!("end of stream received");
|
debug!("end of stream received");
|
||||||
break;
|
return false;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -18,10 +18,14 @@
|
||||||
*/
|
*/
|
||||||
|
|
||||||
use super::super::Middleware;
|
use super::super::Middleware;
|
||||||
use crate::engine::{event::{Event, EventType, ui::{MenuItem, ShowContextMenuEvent, SimpleMenuItem}}};
|
use crate::engine::event::{
|
||||||
|
ui::{MenuItem, ShowContextMenuEvent, SimpleMenuItem},
|
||||||
|
Event, EventType,
|
||||||
|
};
|
||||||
|
|
||||||
pub struct ContextMenuMiddleware {
|
const CONTEXT_ITEM_EXIT: u32 = 0;
|
||||||
}
|
|
||||||
|
pub struct ContextMenuMiddleware {}
|
||||||
|
|
||||||
impl ContextMenuMiddleware {
|
impl ContextMenuMiddleware {
|
||||||
pub fn new() -> Self {
|
pub fn new() -> Self {
|
||||||
|
@ -35,30 +39,38 @@ impl Middleware for ContextMenuMiddleware {
|
||||||
}
|
}
|
||||||
|
|
||||||
fn next(&self, event: Event, _: &mut dyn FnMut(Event)) -> Event {
|
fn next(&self, event: Event, _: &mut dyn FnMut(Event)) -> Event {
|
||||||
if let EventType::TrayIconClicked = event.etype {
|
match &event.etype {
|
||||||
// TODO: fetch top matches for the active config to be added
|
EventType::TrayIconClicked => {
|
||||||
|
// TODO: fetch top matches for the active config to be added
|
||||||
|
|
||||||
// TODO: my idea is to use a set of reserved u32 ids for built-in
|
// TODO: my idea is to use a set of reserved u32 ids for built-in
|
||||||
// actions such as Exit, Open Editor etc
|
// actions such as Exit, Open Editor etc
|
||||||
// then we need some u32 for the matches, so we need to create
|
// then we need some u32 for the matches, so we need to create
|
||||||
// a mapping structure match_id <-> context-menu-id
|
// a mapping structure match_id <-> context-menu-id
|
||||||
return Event::caused_by(
|
return Event::caused_by(
|
||||||
event.source_id,
|
event.source_id,
|
||||||
EventType::ShowContextMenu(ShowContextMenuEvent {
|
EventType::ShowContextMenu(ShowContextMenuEvent {
|
||||||
// TODO: add actual entries
|
// TODO: add actual entries
|
||||||
items: vec![
|
items: vec![MenuItem::Simple(SimpleMenuItem {
|
||||||
MenuItem::Simple(SimpleMenuItem {
|
id: CONTEXT_ITEM_EXIT,
|
||||||
id: 0,
|
|
||||||
label: "Exit espanso".to_string(),
|
label: "Exit espanso".to_string(),
|
||||||
})
|
})],
|
||||||
]
|
}),
|
||||||
}),
|
);
|
||||||
)
|
}
|
||||||
|
EventType::ContextMenuClicked(context_click_event) => {
|
||||||
|
match context_click_event.context_item_id {
|
||||||
|
CONTEXT_ITEM_EXIT => {
|
||||||
|
Event::caused_by(event.source_id, EventType::ExitRequested(true))
|
||||||
|
}
|
||||||
|
custom => {
|
||||||
|
// TODO: handle dynamic items
|
||||||
|
todo!()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
_ => event,
|
||||||
}
|
}
|
||||||
|
|
||||||
// TODO: handle context menu clicks
|
|
||||||
|
|
||||||
event
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -36,9 +36,9 @@ impl Middleware for ExitMiddleware {
|
||||||
}
|
}
|
||||||
|
|
||||||
fn next(&self, event: Event, _: &mut dyn FnMut(Event)) -> Event {
|
fn next(&self, event: Event, _: &mut dyn FnMut(Event)) -> Event {
|
||||||
if let EventType::ExitRequested = &event.etype {
|
if let EventType::ExitRequested(exit_all_processes) = &event.etype {
|
||||||
debug!("received ExitRequested event, dispatching exit");
|
debug!("received ExitRequested event with 'exit_all_processes: {}', dispatching exit", exit_all_processes);
|
||||||
return Event::caused_by(event.source_id, EventType::Exit);
|
return Event::caused_by(event.source_id, EventType::Exit(*exit_all_processes));
|
||||||
}
|
}
|
||||||
|
|
||||||
event
|
event
|
||||||
|
|
27
espanso/src/exit_code.rs
Normal file
27
espanso/src/exit_code.rs
Normal file
|
@ -0,0 +1,27 @@
|
||||||
|
/*
|
||||||
|
* 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/>.
|
||||||
|
*/
|
||||||
|
|
||||||
|
pub const WORKER_SUCCESS: i32 = 0;
|
||||||
|
pub const WORKER_ALREADY_RUNNING: i32 = 1;
|
||||||
|
pub const WORKER_GENERAL_ERROR: i32 = 2;
|
||||||
|
pub const WORKER_EXIT_ALL_PROCESSES: i32 = 101;
|
||||||
|
|
||||||
|
pub const DAEMON_SUCCESS: i32 = 0;
|
||||||
|
pub const DAEMON_ALREADY_RUNNING: i32 = 1;
|
||||||
|
pub const DAEMON_GENERAL_ERROR: i32 = 2;
|
|
@ -37,6 +37,7 @@ use crate::cli::LogMode;
|
||||||
|
|
||||||
mod cli;
|
mod cli;
|
||||||
mod engine;
|
mod engine;
|
||||||
|
mod exit_code;
|
||||||
mod gui;
|
mod gui;
|
||||||
mod ipc;
|
mod ipc;
|
||||||
mod lock;
|
mod lock;
|
||||||
|
|
Loading…
Reference in New Issue
Block a user