use crate::error::{Error, Result};
use fs2::FileExt;
use std::{fs::File, path::Path};
#[derive(Debug)]
pub struct InstanceLock {
file: Option<File>,
path: String,
}
impl InstanceLock {
pub fn new<P: AsRef<Path>>(path: P) -> Self {
let path_str = path.as_ref().to_string_lossy().to_string();
Self {
file: None,
path: path_str,
}
}
pub fn lock(&mut self) -> Result<()> {
let file = File::options()
.read(true)
.write(true)
.create(true)
.truncate(true)
.open(&self.path)
.map_err(|e| {
Error::io_with_source(
format!("Failed to open or create lock file at {}", self.path),
e,
)
})?;
file.try_lock_exclusive().map_err(|e| {
Error::runtime_with_source(
format!(
"Failed to acquire exclusive lock on {}, another instance may be running",
self.path
),
e,
)
})?;
self.file = Some(file);
Ok(())
}
pub fn unlock(&mut self) -> Result<()> {
if let Some(file) = self.file.take() {
fs2::FileExt::unlock(&file).map_err(|e| {
Error::io_with_source(format!("Failed to release lock on file {}", self.path), e)
})?;
}
Ok(())
}
#[must_use]
pub const fn is_locked(&self) -> bool {
self.file.is_some()
}
}
impl Drop for InstanceLock {
fn drop(&mut self) {
if self.is_locked() {
let _ = self.unlock();
}
}
}
#[cfg(test)]
mod tests {
use super::*;
use tempfile::tempdir;
#[cfg_attr(miri, ignore)]
#[test]
fn test_lock_acquisition() {
let dir = tempdir().expect("Failed to create temporary directory");
let lock_path = dir.path().join("test.lock");
let mut lock = InstanceLock::new(&lock_path);
assert!(lock.lock().is_ok());
assert!(lock.is_locked());
let mut lock2 = InstanceLock::new(&lock_path);
assert!(lock2.lock().is_err());
assert!(!lock2.is_locked());
assert!(lock.unlock().is_ok());
assert!(!lock.is_locked());
assert!(lock2.lock().is_ok());
assert!(lock2.is_locked());
}
#[cfg_attr(miri, ignore)]
#[test]
fn test_lock_drop() {
let dir = tempdir().expect("Failed to create temporary directory");
let lock_path = dir.path().join("drop_test.lock");
{
let mut lock = InstanceLock::new(&lock_path);
assert!(lock.lock().is_ok());
}
let mut lock2 = InstanceLock::new(&lock_path);
assert!(lock2.lock().is_ok());
}
}