feat(core): prevent multiple daemon and worker instances

This commit is contained in:
Federico Terzi 2021-05-16 16:55:40 +02:00
parent e08bf2f69a
commit 22ba3a5e03
6 changed files with 119 additions and 9 deletions

11
Cargo.lock generated
View File

@ -311,6 +311,7 @@ dependencies = [
"espanso-path",
"espanso-render",
"espanso-ui",
"fs2",
"html2text",
"lazy_static",
"log",
@ -484,6 +485,16 @@ version = "1.0.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
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]]
name = "fuchsia-cprng"
version = "0.1.1"

View File

@ -37,4 +37,5 @@ serde = { version = "1.0.123", features = ["derive"] }
serde_json = "1.0.62"
markdown = "0.3.0"
html2text = "0.2.1"
log-panics = "2.0.0"
log-panics = "2.0.0"
fs2 = "0.4.3"

View File

@ -19,7 +19,9 @@
use std::process::Command;
use log::info;
use log::{error, info};
use crate::lock::acquire_daemon_lock;
use super::{CliModule, CliModuleArgs};
@ -41,16 +43,20 @@ const VERSION: &str = env!("CARGO_PKG_VERSION");
fn daemon_main(args: CliModuleArgs) {
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);
// 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: start IPC server
// TODO: start file watcher thread
// TODO: register signals to terminate the worker if the daemon terminates
let espanso_exe_path =
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");
// TODO: start IPC server
// TODO: start file watcher thread
loop {
std::thread::sleep(std::time::Duration::from_millis(1000));
}
}

View File

@ -17,6 +17,10 @@
* 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 super::{CliModule, CliModuleArgs};
@ -39,6 +43,15 @@ pub fn new() -> CliModule {
}
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
.config_store
.expect("missing config store in worker main");
@ -46,8 +59,6 @@ fn worker_main(args: CliModuleArgs) {
.match_store
.expect("missing match store in worker main");
let paths = args.paths.expect("missing paths in worker main");
let icon_paths =
self::ui::icon::load_icon_paths(&paths.runtime).expect("unable to initialize icons");

72
espanso/src/lock.rs Normal file
View 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")
}

View File

@ -35,6 +35,7 @@ use crate::cli::LogMode;
mod cli;
mod engine;
mod gui;
mod lock;
mod logging;
mod util;
@ -253,7 +254,7 @@ fn main() {
if handler.enable_logs {
let config = ConfigBuilder::new()
.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)
.add_filter_ignore_str("html5ever")
.build();