feat(core): prevent multiple daemon and worker instances
This commit is contained in:
		
							parent
							
								
									e08bf2f69a
								
							
						
					
					
						commit
						22ba3a5e03
					
				
							
								
								
									
										11
									
								
								Cargo.lock
									
									
									
										generated
									
									
									
								
							
							
						
						
									
										11
									
								
								Cargo.lock
									
									
									
										generated
									
									
									
								
							|  | @ -311,6 +311,7 @@ dependencies = [ | ||||||
|  "espanso-path", |  "espanso-path", | ||||||
|  "espanso-render", |  "espanso-render", | ||||||
|  "espanso-ui", |  "espanso-ui", | ||||||
|  |  "fs2", | ||||||
|  "html2text", |  "html2text", | ||||||
|  "lazy_static", |  "lazy_static", | ||||||
|  "log", |  "log", | ||||||
|  | @ -484,6 +485,16 @@ version = "1.0.0" | ||||||
| source = "registry+https://github.com/rust-lang/crates.io-index" | source = "registry+https://github.com/rust-lang/crates.io-index" | ||||||
| checksum = "69a039c3498dc930fe810151a34ba0c1c70b02b8625035592e74432f678591f2" | checksum = "69a039c3498dc930fe810151a34ba0c1c70b02b8625035592e74432f678591f2" | ||||||
| 
 | 
 | ||||||
|  | [[package]] | ||||||
|  | name = "fs2" | ||||||
|  | version = "0.4.3" | ||||||
|  | source = "registry+https://github.com/rust-lang/crates.io-index" | ||||||
|  | checksum = "9564fc758e15025b46aa6643b1b77d047d1a56a1aea6e01002ac0c7026876213" | ||||||
|  | dependencies = [ | ||||||
|  |  "libc", | ||||||
|  |  "winapi", | ||||||
|  | ] | ||||||
|  | 
 | ||||||
| [[package]] | [[package]] | ||||||
| name = "fuchsia-cprng" | name = "fuchsia-cprng" | ||||||
| version = "0.1.1" | version = "0.1.1" | ||||||
|  |  | ||||||
|  | @ -38,3 +38,4 @@ serde_json = "1.0.62" | ||||||
| markdown = "0.3.0" | markdown = "0.3.0" | ||||||
| html2text = "0.2.1" | html2text = "0.2.1" | ||||||
| log-panics = "2.0.0" | log-panics = "2.0.0" | ||||||
|  | fs2 = "0.4.3" | ||||||
|  | @ -19,7 +19,9 @@ | ||||||
| 
 | 
 | ||||||
| use std::process::Command; | use std::process::Command; | ||||||
| 
 | 
 | ||||||
| use log::info; | use log::{error, info}; | ||||||
|  | 
 | ||||||
|  | use crate::lock::acquire_daemon_lock; | ||||||
| 
 | 
 | ||||||
| use super::{CliModule, CliModuleArgs}; | use super::{CliModule, CliModuleArgs}; | ||||||
| 
 | 
 | ||||||
|  | @ -41,16 +43,20 @@ const VERSION: &str = env!("CARGO_PKG_VERSION"); | ||||||
| fn daemon_main(args: CliModuleArgs) { | fn daemon_main(args: CliModuleArgs) { | ||||||
|   let paths = args.paths.expect("missing paths in daemon main"); |   let paths = args.paths.expect("missing paths in daemon main"); | ||||||
| 
 | 
 | ||||||
|  |   // Make sure only one instance of the daemon is running
 | ||||||
|  |   let lock_file = acquire_daemon_lock(&paths.runtime); | ||||||
|  |   if lock_file.is_none() { | ||||||
|  |     error!("daemon is already running!"); | ||||||
|  |     std::process::exit(1); | ||||||
|  |   } | ||||||
|  | 
 | ||||||
|   info!("espanso version: {}", VERSION); |   info!("espanso version: {}", VERSION); | ||||||
|   // TODO: print os system and version? (with os_info crate)
 |   // TODO: print os system and version? (with os_info crate)
 | ||||||
| 
 | 
 | ||||||
|   // TODO: check daemon lock file to avoid duplicates
 |  | ||||||
| 
 | 
 | ||||||
|   // TODO: check worker lock file, if taken stop the worker process through IPC
 |   // TODO: check worker lock file, if taken stop the worker process through IPC
 | ||||||
| 
 | 
 | ||||||
|   // TODO: start IPC server
 |   // TODO: register signals to terminate the worker if the daemon terminates
 | ||||||
| 
 |  | ||||||
|   // TODO: start file watcher thread
 |  | ||||||
| 
 | 
 | ||||||
|   let espanso_exe_path = |   let espanso_exe_path = | ||||||
|     std::env::current_exe().expect("unable to obtain espanso executable location"); |     std::env::current_exe().expect("unable to obtain espanso executable location"); | ||||||
|  | @ -71,4 +77,12 @@ fn daemon_main(args: CliModuleArgs) { | ||||||
|   } |   } | ||||||
| 
 | 
 | ||||||
|   command.spawn().expect("unable to spawn worker process"); |   command.spawn().expect("unable to spawn worker process"); | ||||||
|  | 
 | ||||||
|  |   // TODO: start IPC server
 | ||||||
|  | 
 | ||||||
|  |   // TODO: start file watcher thread
 | ||||||
|  | 
 | ||||||
|  |   loop { | ||||||
|  |     std::thread::sleep(std::time::Duration::from_millis(1000)); | ||||||
|  |   } | ||||||
| } | } | ||||||
|  |  | ||||||
|  | @ -17,6 +17,10 @@ | ||||||
|  * along with espanso.  If not, see <https://www.gnu.org/licenses/>.
 |  * along with espanso.  If not, see <https://www.gnu.org/licenses/>.
 | ||||||
|  */ |  */ | ||||||
| 
 | 
 | ||||||
|  | use log::error; | ||||||
|  | 
 | ||||||
|  | use crate::lock::acquire_worker_lock; | ||||||
|  | 
 | ||||||
| use self::ui::util::convert_icon_paths_to_tray_vec; | use self::ui::util::convert_icon_paths_to_tray_vec; | ||||||
| 
 | 
 | ||||||
| use super::{CliModule, CliModuleArgs}; | use super::{CliModule, CliModuleArgs}; | ||||||
|  | @ -39,6 +43,15 @@ pub fn new() -> CliModule { | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| fn worker_main(args: CliModuleArgs) { | fn worker_main(args: CliModuleArgs) { | ||||||
|  |   let paths = args.paths.expect("missing paths in worker main"); | ||||||
|  | 
 | ||||||
|  |   // Avoid running multiple worker instances
 | ||||||
|  |   let lock_file = acquire_worker_lock(&paths.runtime); | ||||||
|  |   if lock_file.is_none() { | ||||||
|  |     error!("worker is already running!"); | ||||||
|  |     std::process::exit(1); | ||||||
|  |   } | ||||||
|  | 
 | ||||||
|   let config_store = args |   let config_store = args | ||||||
|     .config_store |     .config_store | ||||||
|     .expect("missing config store in worker main"); |     .expect("missing config store in worker main"); | ||||||
|  | @ -46,8 +59,6 @@ fn worker_main(args: CliModuleArgs) { | ||||||
|     .match_store |     .match_store | ||||||
|     .expect("missing match store in worker main"); |     .expect("missing match store in worker main"); | ||||||
| 
 | 
 | ||||||
|   let paths = args.paths.expect("missing paths in worker main"); |  | ||||||
| 
 |  | ||||||
|   let icon_paths = |   let icon_paths = | ||||||
|     self::ui::icon::load_icon_paths(&paths.runtime).expect("unable to initialize icons"); |     self::ui::icon::load_icon_paths(&paths.runtime).expect("unable to initialize icons"); | ||||||
| 
 | 
 | ||||||
|  |  | ||||||
							
								
								
									
										72
									
								
								espanso/src/lock.rs
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										72
									
								
								espanso/src/lock.rs
									
									
									
									
									
										Normal file
									
								
							|  | @ -0,0 +1,72 @@ | ||||||
|  | /* | ||||||
|  |  * 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 fs2::FileExt; | ||||||
|  | use std::{ | ||||||
|  |   fs::{File, OpenOptions}, | ||||||
|  |   path::Path, | ||||||
|  | }; | ||||||
|  | 
 | ||||||
|  | pub struct Lock { | ||||||
|  |   lock_file: File, | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | impl Lock { | ||||||
|  |   pub fn release(self) -> Result<()> { | ||||||
|  |     self.lock_file.unlock()?; | ||||||
|  |     Ok(()) | ||||||
|  |   } | ||||||
|  | 
 | ||||||
|  |   fn acquire(runtime_dir: &Path, name: &str) -> Option<Lock> { | ||||||
|  |     let lock_file_path = runtime_dir.join(format!("{}.lock", name)); | ||||||
|  |     let lock_file = OpenOptions::new() | ||||||
|  |       .read(true) | ||||||
|  |       .write(true) | ||||||
|  |       .create(true) | ||||||
|  |       .open(&lock_file_path) | ||||||
|  |       .expect(&format!( | ||||||
|  |         "unable to create reference to lock file: {:?}", | ||||||
|  |         lock_file_path | ||||||
|  |       )); | ||||||
|  | 
 | ||||||
|  |     if lock_file.try_lock_exclusive().is_ok() { | ||||||
|  |       Some(Lock { lock_file }) | ||||||
|  |     } else { | ||||||
|  |       None | ||||||
|  |     } | ||||||
|  |   } | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | impl Drop for Lock { | ||||||
|  |   fn drop(&mut self) { | ||||||
|  |     self | ||||||
|  |       .lock_file | ||||||
|  |       .unlock() | ||||||
|  |       .expect(&format!("unable to unlock lock_file: {:?}", self.lock_file)); | ||||||
|  |   } | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | pub fn acquire_daemon_lock(runtime_dir: &Path) -> Option<Lock> { | ||||||
|  |   Lock::acquire(runtime_dir, "espanso-daemon.lock") | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | pub fn acquire_worker_lock(runtime_dir: &Path) -> Option<Lock> { | ||||||
|  |   Lock::acquire(runtime_dir, "espanso-worker.lock") | ||||||
|  | } | ||||||
|  | @ -35,6 +35,7 @@ use crate::cli::LogMode; | ||||||
| mod cli; | mod cli; | ||||||
| mod engine; | mod engine; | ||||||
| mod gui; | mod gui; | ||||||
|  | mod lock; | ||||||
| mod logging; | mod logging; | ||||||
| mod util; | mod util; | ||||||
| 
 | 
 | ||||||
|  | @ -253,7 +254,7 @@ fn main() { | ||||||
|     if handler.enable_logs { |     if handler.enable_logs { | ||||||
|       let config = ConfigBuilder::new() |       let config = ConfigBuilder::new() | ||||||
|         .set_time_to_local(true) |         .set_time_to_local(true) | ||||||
|         .set_time_format(format!("%H:%M:%S [{}]", handler.subcommand)) |         .set_time_format(format!("%H:%M:%S [{}({})]", handler.subcommand, std::process::id())) | ||||||
|         .set_location_level(LevelFilter::Off) |         .set_location_level(LevelFilter::Off) | ||||||
|         .add_filter_ignore_str("html5ever") |         .add_filter_ignore_str("html5ever") | ||||||
|         .build(); |         .build(); | ||||||
|  |  | ||||||
		Loading…
	
		Reference in New Issue
	
	Block a user