feat(app): prevent multiple instances
This commit is contained in:
parent
7a8b0e6ce7
commit
3f517c5f48
|
|
@ -1156,6 +1156,16 @@ dependencies = [
|
|||
"percent-encoding",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "fs2"
|
||||
version = "0.4.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "9564fc758e15025b46aa6643b1b77d047d1a56a1aea6e01002ac0c7026876213"
|
||||
dependencies = [
|
||||
"libc",
|
||||
"winapi",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "futures"
|
||||
version = "0.3.32"
|
||||
|
|
@ -1265,6 +1275,7 @@ dependencies = [
|
|||
"axum",
|
||||
"chrono",
|
||||
"dotenv",
|
||||
"fs2",
|
||||
"serde",
|
||||
"serde_json",
|
||||
"serde_with",
|
||||
|
|
|
|||
|
|
@ -43,6 +43,7 @@ validator = { version = "0.20", features = ["derive"] }
|
|||
|
||||
# Error handling
|
||||
anyhow = "1.0"
|
||||
fs2 = "0.4"
|
||||
|
||||
[target.'cfg(windows)'.dependencies]
|
||||
tray-icon = "0.15"
|
||||
|
|
|
|||
13
src/main.rs
13
src/main.rs
|
|
@ -1,3 +1,5 @@
|
|||
#![cfg_attr(all(windows, not(debug_assertions)), windows_subsystem = "windows")]
|
||||
|
||||
mod control;
|
||||
mod config;
|
||||
mod connection;
|
||||
|
|
@ -47,6 +49,17 @@ pub struct AppState {
|
|||
async fn main() {
|
||||
dotenv::dotenv().ok();
|
||||
util::log::init_logger();
|
||||
let _single_instance = match util::single_instance::try_acquire("PLCControl.Gateway") {
|
||||
Ok(guard) => guard,
|
||||
Err(err) if err.kind() == std::io::ErrorKind::AlreadyExists => {
|
||||
tracing::warn!("Another PLC Control instance is already running");
|
||||
return;
|
||||
}
|
||||
Err(err) => {
|
||||
tracing::error!("Failed to initialize single instance guard: {}", err);
|
||||
return;
|
||||
}
|
||||
};
|
||||
|
||||
let config = AppConfig::from_env().expect("Failed to load configuration");
|
||||
let pool = init_database(&config.database_url)
|
||||
|
|
|
|||
|
|
@ -2,4 +2,5 @@ pub mod datetime;
|
|||
pub mod log;
|
||||
pub mod pagination;
|
||||
pub mod response;
|
||||
pub mod validator;
|
||||
pub mod single_instance;
|
||||
pub mod validator;
|
||||
|
|
|
|||
|
|
@ -0,0 +1,65 @@
|
|||
use fs2::FileExt;
|
||||
use std::{
|
||||
fs::{File, OpenOptions},
|
||||
io,
|
||||
path::PathBuf,
|
||||
};
|
||||
|
||||
pub fn try_acquire(name: &str) -> io::Result<SingleInstanceGuard> {
|
||||
SingleInstanceGuard::acquire(name)
|
||||
}
|
||||
|
||||
pub struct SingleInstanceGuard {
|
||||
_file: File,
|
||||
}
|
||||
|
||||
impl SingleInstanceGuard {
|
||||
fn acquire(name: &str) -> io::Result<Self> {
|
||||
let lock_path = lock_file_path(name);
|
||||
let file = OpenOptions::new()
|
||||
.create(true)
|
||||
.read(true)
|
||||
.write(true)
|
||||
.truncate(false)
|
||||
.open(lock_path)?;
|
||||
|
||||
if let Err(err) = file.try_lock_exclusive() {
|
||||
if err.kind() == io::ErrorKind::WouldBlock {
|
||||
return Err(io::Error::new(
|
||||
io::ErrorKind::AlreadyExists,
|
||||
"another PLC Control instance is already running",
|
||||
));
|
||||
}
|
||||
return Err(io::Error::new(
|
||||
err.kind(),
|
||||
format!("failed to lock single-instance file: {}", err),
|
||||
));
|
||||
}
|
||||
|
||||
Ok(Self { _file: file })
|
||||
}
|
||||
}
|
||||
|
||||
fn lock_file_path(name: &str) -> PathBuf {
|
||||
let sanitized: String = name
|
||||
.chars()
|
||||
.map(|ch| match ch {
|
||||
'a'..='z' | 'A'..='Z' | '0'..='9' | '-' | '_' => ch,
|
||||
_ => '_',
|
||||
})
|
||||
.collect();
|
||||
|
||||
std::env::temp_dir().join(format!("{}.lock", sanitized))
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::lock_file_path;
|
||||
|
||||
#[test]
|
||||
fn lock_path_is_sanitized() {
|
||||
let path = lock_file_path("PLCControl/Gateway:test");
|
||||
let path_str = path.to_string_lossy();
|
||||
assert!(path_str.ends_with("PLCControl_Gateway_test.lock"));
|
||||
}
|
||||
}
|
||||
Loading…
Reference in New Issue