use std::fs::File;
use std::os::unix::io::AsRawFd;
use nix::fcntl::{flock, FlockArg};
use nix::Result;
pub trait LockDo {
fn do_exclusive<F, T>(&self, action: F) -> Result<T>
where
F: FnOnce() -> T;
fn do_shared<F, T>(&self, action: F) -> Result<T>
where
F: FnOnce() -> T;
}
impl LockDo for File {
fn do_exclusive<F, T>(&self, action: F) -> Result<T>
where
F: FnOnce() -> T,
{
let guard = FileLockGuard(&self);
flock(self.as_raw_fd(), FlockArg::LockExclusive)?;
let result = action();
drop(guard); Ok(result)
}
fn do_shared<F, T>(&self, action: F) -> Result<T>
where
F: FnOnce() -> T,
{
let guard = FileLockGuard(&self);
flock(self.as_raw_fd(), FlockArg::LockShared)?;
let result = action();
drop(guard); Ok(result)
}
}
struct FileLockGuard<'a>(&'a File);
impl<'a> Drop for FileLockGuard<'a> {
fn drop(&mut self) {
flock(self.0.as_raw_fd(), FlockArg::Unlock).unwrap()
}
}
#[cfg(test)]
mod tests {
extern crate tempdir;
use super::LockDo;
use std::fs::OpenOptions;
use std::os::unix::io::AsRawFd;
use std::path::Path;
use nix::fcntl::{flock, FlockArg};
fn can_lock<P: AsRef<Path>>(filename: P, exclusive: bool) -> bool {
let file = OpenOptions::new()
.append(true)
.create(true)
.open(filename)
.unwrap();
let mode = match exclusive {
true => FlockArg::LockExclusiveNonblock,
false => FlockArg::LockSharedNonblock,
};
match flock(file.as_raw_fd(), mode) {
Ok(_) => true,
Err(_) => false,
}
}
fn can_lock_exclusive<P: AsRef<Path>>(filename: P) -> bool {
can_lock(filename, true)
}
fn can_lock_shared<P: AsRef<Path>>(filename: P) -> bool {
can_lock(filename, false)
}
#[test]
fn file_do_exclusive_takes_exclusive_flock() {
let lock_dir = tempdir::TempDir::new("locks").unwrap();
let lock_filename = lock_dir.path().join("lock");
let lock_file = OpenOptions::new()
.append(true)
.create(true)
.open(&lock_filename)
.unwrap();
assert!(can_lock_exclusive(&lock_filename));
assert!(can_lock_shared(&lock_filename));
lock_file
.do_exclusive(|| {
assert!(!can_lock_exclusive(&lock_filename));
assert!(!can_lock_shared(&lock_filename));
})
.unwrap();
assert!(can_lock_exclusive(&lock_filename));
assert!(can_lock_shared(&lock_filename));
}
#[test]
fn file_do_shared_takes_shared_flock() {
let lock_dir = tempdir::TempDir::new("locks").unwrap();
let lock_filename = lock_dir.path().join("lock");
let lock_file = OpenOptions::new()
.append(true)
.create(true)
.open(&lock_filename)
.unwrap();
assert!(can_lock_exclusive(&lock_filename));
assert!(can_lock_shared(&lock_filename));
lock_file
.do_shared(|| {
assert!(!can_lock_exclusive(&lock_filename));
assert!(can_lock_shared(&lock_filename));
})
.unwrap();
assert!(can_lock_exclusive(&lock_filename));
assert!(can_lock_shared(&lock_filename));
}
}