7a4e9764b1
- Полностью переписан swap-модуль в режиме exclusive ownership\n- Добавлены verify/checks, статус фактического swap-state и drift\n- Интегрирован ensure-swap в daemon и systemd ExecStartPre\n- Усилен lock API (таймауты) и применение в traffic/firewall/swap\n- Усилен parser firewall (JSON-first + устойчивый text fallback)\n- Обновлены config validation, runtime tools и default priority\n- Синхронизированы docs и build/package hygiene\n- Удалены member Cargo.lock в пользу корневого lockfile
103 lines
3.1 KiB
Rust
103 lines
3.1 KiB
Rust
use std::{
|
|
fs::{self, OpenOptions},
|
|
io::Write,
|
|
path::Path,
|
|
thread,
|
|
time::{Duration, Instant},
|
|
};
|
|
|
|
pub struct FileLock {
|
|
path: String,
|
|
}
|
|
|
|
impl FileLock {
|
|
pub fn acquire(path: &str) -> anyhow::Result<Self> {
|
|
Self::acquire_timeout(path, Duration::from_secs(0))
|
|
}
|
|
|
|
pub fn acquire_timeout(path: &str, timeout: Duration) -> anyhow::Result<Self> {
|
|
let deadline = Instant::now() + timeout;
|
|
loop {
|
|
match Self::try_acquire_once(path) {
|
|
Ok(lock) => return Ok(lock),
|
|
Err(e) => {
|
|
let msg = e.to_string();
|
|
if !msg.contains("lock already exists") {
|
|
return Err(e);
|
|
}
|
|
if Instant::now() >= deadline {
|
|
anyhow::bail!("lock busy timeout: {path}");
|
|
}
|
|
thread::sleep(Duration::from_millis(100));
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
fn try_acquire_once(path: &str) -> anyhow::Result<Self> {
|
|
let p = Path::new(path);
|
|
if let Some(parent) = p.parent() {
|
|
fs::create_dir_all(parent)?;
|
|
}
|
|
match OpenOptions::new().create_new(true).write(true).open(p) {
|
|
Ok(mut file) => {
|
|
file.write_all(std::process::id().to_string().as_bytes())?;
|
|
return Ok(Self {
|
|
path: path.to_string(),
|
|
});
|
|
}
|
|
Err(e) if e.kind() == std::io::ErrorKind::AlreadyExists => {
|
|
let stale = is_stale_lock(p)?;
|
|
if stale {
|
|
let _ = fs::remove_file(p);
|
|
let mut file = OpenOptions::new().create_new(true).write(true).open(p)?;
|
|
file.write_all(std::process::id().to_string().as_bytes())?;
|
|
return Ok(Self {
|
|
path: path.to_string(),
|
|
});
|
|
}
|
|
anyhow::bail!("lock already exists: {path}");
|
|
}
|
|
Err(e) => return Err(e.into()),
|
|
}
|
|
}
|
|
}
|
|
|
|
impl Drop for FileLock {
|
|
fn drop(&mut self) {
|
|
let _ = fs::remove_file(&self.path);
|
|
}
|
|
}
|
|
|
|
fn is_stale_lock(path: &Path) -> anyhow::Result<bool> {
|
|
let pid_raw = fs::read_to_string(path)?;
|
|
let pid = match pid_raw.trim().parse::<u32>() {
|
|
Ok(v) => v,
|
|
Err(_) => return Ok(true),
|
|
};
|
|
|
|
let proc_path = format!("/proc/{pid}");
|
|
Ok(!Path::new(&proc_path).exists())
|
|
}
|
|
|
|
#[cfg(test)]
|
|
mod tests {
|
|
use super::*;
|
|
|
|
#[test]
|
|
fn second_acquire_fails_for_live_lock() {
|
|
let p = format!("/tmp/awg2_harness_lock_test_{}", std::process::id());
|
|
let _first = FileLock::acquire(&p).expect("first lock");
|
|
let second = FileLock::acquire(&p);
|
|
assert!(second.is_err());
|
|
}
|
|
|
|
#[test]
|
|
fn acquire_timeout_times_out_for_live_lock() {
|
|
let p = format!("/tmp/awg2_harness_lock_test_timeout_{}", std::process::id());
|
|
let _first = FileLock::acquire(&p).expect("first lock");
|
|
let second = FileLock::acquire_timeout(&p, Duration::from_millis(250));
|
|
assert!(second.is_err());
|
|
}
|
|
}
|