use std::fs::{File, TryLockError};
use std::io;
use std::path::Path;
#[derive(Debug)]
pub struct ProcessLock {
_file: File,
}
#[derive(Debug)]
pub enum TryLockResult {
Acquired(ProcessLock),
Contended,
}
pub fn try_acquire(path: &Path) -> io::Result<TryLockResult> {
let file = File::options()
.create(true)
.read(true)
.write(true)
.truncate(false)
.open(path)?;
match file.try_lock() {
Ok(()) => Ok(TryLockResult::Acquired(ProcessLock { _file: file })),
Err(TryLockError::WouldBlock) => Ok(TryLockResult::Contended),
Err(TryLockError::Error(e)) => Err(e),
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn second_acquire_is_contended_then_reacquirable_after_drop() {
let dir = tempfile::tempdir().unwrap();
let path = dir.path().join("test.lock");
let first = match try_acquire(&path).unwrap() {
TryLockResult::Acquired(g) => g,
TryLockResult::Contended => panic!("first acquire should succeed"),
};
match try_acquire(&path).unwrap() {
TryLockResult::Contended => {}
TryLockResult::Acquired(_) => panic!("second acquire should be contended"),
}
drop(first);
let third = match try_acquire(&path).unwrap() {
TryLockResult::Acquired(g) => g,
TryLockResult::Contended => panic!("acquire after drop should succeed"),
};
drop(third);
assert!(path.exists(), "lock file should persist on disk");
match try_acquire(&path).unwrap() {
TryLockResult::Acquired(_) => {}
TryLockResult::Contended => panic!("stale lock file should be re-lockable"),
}
}
}