Files
AWG2_harness/traffic_core/src/lock.rs
T
founder 7a4e9764b1 fix7: продакшен-реализация полного reconcile swap и интеграций
- Полностью переписан 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
2026-05-11 17:30:15 +05:00

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());
}
}