Skip to content

Commit afd024a

Browse files
committed
feat(client):support single instance
1 parent f14470a commit afd024a

File tree

2 files changed

+111
-0
lines changed

2 files changed

+111
-0
lines changed

aw-client-rust/src/lib.rs

+1
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@ extern crate serde_json;
66
extern crate tokio;
77

88
pub mod blocking;
9+
pub mod single_instance;
910

1011
use std::{collections::HashMap, error::Error};
1112

aw-client-rust/src/single_instance.rs

+110
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,110 @@
1+
use log::{debug, error};
2+
use std::fs::{File, OpenOptions};
3+
use std::io;
4+
use std::path::{Path, PathBuf};
5+
use std::sync::atomic::{AtomicBool, Ordering};
6+
use std::sync::Arc;
7+
8+
#[cfg(not(windows))]
9+
use std::os::unix::io::AsRawFd;
10+
11+
#[derive(Debug)]
12+
pub struct SingleInstance {
13+
#[cfg(windows)]
14+
handle: Option<File>,
15+
#[cfg(not(windows))]
16+
file: Option<File>,
17+
lockfile: PathBuf,
18+
locked: Arc<AtomicBool>,
19+
}
20+
21+
#[derive(Debug, thiserror::Error)]
22+
pub enum SingleInstanceError {
23+
#[error("Another instance is already running")]
24+
AlreadyRunning,
25+
#[error("IO error: {0}")]
26+
Io(#[from] io::Error),
27+
#[error("Failed to create lock directory")]
28+
LockDirCreation,
29+
}
30+
31+
impl SingleInstance {
32+
pub fn new<P: AsRef<Path>>(
33+
cache_dir: P,
34+
client_name: &str,
35+
) -> Result<Self, SingleInstanceError> {
36+
let lock_dir = cache_dir.as_ref().join("client_locks");
37+
std::fs::create_dir_all(&lock_dir).map_err(|_| SingleInstanceError::LockDirCreation)?;
38+
39+
let lockfile = lock_dir.join(client_name);
40+
debug!("SingleInstance lockfile: {:?}", lockfile);
41+
42+
#[cfg(windows)]
43+
{
44+
// On Windows, try to create an exclusive file
45+
// Remove existing file if it exists (in case of previous crash)
46+
let _ = std::fs::remove_file(&lockfile);
47+
48+
match OpenOptions::new()
49+
.write(true)
50+
.create(true)
51+
.create_new(true)
52+
.open(&lockfile)
53+
{
54+
Ok(handle) => Ok(SingleInstance {
55+
handle: Some(handle),
56+
lockfile,
57+
locked: Arc::new(AtomicBool::new(true)),
58+
}),
59+
Err(e) if e.kind() == io::ErrorKind::AlreadyExists => {
60+
error!("Another instance is already running");
61+
Err(SingleInstanceError::AlreadyRunning)
62+
}
63+
Err(e) => Err(SingleInstanceError::Io(e)),
64+
}
65+
}
66+
67+
#[cfg(unix)]
68+
{
69+
// On Unix-like systems, use flock
70+
match OpenOptions::new().write(true).create(true).open(&lockfile) {
71+
Ok(file) => {
72+
let fd = file.as_raw_fd();
73+
match unsafe { libc::flock(fd, libc::LOCK_EX | libc::LOCK_NB) } {
74+
0 => Ok(SingleInstance {
75+
file: Some(file),
76+
lockfile,
77+
locked: Arc::new(AtomicBool::new(true)),
78+
}),
79+
_ => {
80+
error!("Another instance is already running");
81+
Err(SingleInstanceError::AlreadyRunning)
82+
}
83+
}
84+
}
85+
Err(e) => Err(SingleInstanceError::Io(e)),
86+
}
87+
}
88+
}
89+
}
90+
91+
impl Drop for SingleInstance {
92+
fn drop(&mut self) {
93+
if self.locked.load(Ordering::SeqCst) {
94+
#[cfg(windows)]
95+
{
96+
// On Windows, drop the handle and remove the file
97+
self.handle.take();
98+
let _ = std::fs::remove_file(&self.lockfile);
99+
}
100+
101+
#[cfg(unix)]
102+
{
103+
// On Unix, the flock is automatically released when the file is closed
104+
self.file.take();
105+
}
106+
107+
self.locked.store(false, Ordering::SeqCst);
108+
}
109+
}
110+
}

0 commit comments

Comments
 (0)