Skip to content

Commit 6a7fa3d

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

File tree

2 files changed

+114
-0
lines changed

2 files changed

+114
-0
lines changed

aw-client-rust/src/lib.rs

Lines changed: 1 addition & 0 deletions
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

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

0 commit comments

Comments
 (0)