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