156 lines
4.2 KiB
Rust
156 lines
4.2 KiB
Rust
|
//! Inter-process reader-writer lock builder.
|
||
|
//!
|
||
|
//! This implemenation uses fcntl record locks with non-blocking
|
||
|
//! F_SETLK command (never blocks).
|
||
|
|
||
|
use failure::*;
|
||
|
|
||
|
use std::sync::{Arc, Mutex};
|
||
|
use std::os::unix::io::AsRawFd;
|
||
|
|
||
|
// fixme: use F_OFD_ locks when implemented with nix::fcntl
|
||
|
|
||
|
// Note: flock lock conversion is not atomic, so we need to use fcntl
|
||
|
|
||
|
/// Inter-process reader-writer lock
|
||
|
pub struct ProcessLocker {
|
||
|
file: std::fs::File,
|
||
|
exclusive: bool,
|
||
|
writers: usize,
|
||
|
}
|
||
|
|
||
|
/// Lock guard for shared locks
|
||
|
///
|
||
|
/// Release the lock when it goes out of scope.
|
||
|
pub struct ProcessLockSharedGuard {
|
||
|
locker: Arc<Mutex<ProcessLocker>>,
|
||
|
}
|
||
|
|
||
|
impl Drop for ProcessLockSharedGuard {
|
||
|
fn drop(&mut self) {
|
||
|
let mut data = self.locker.lock().unwrap();
|
||
|
|
||
|
if data.writers == 0 { panic!("unexpected ProcessLocker state"); }
|
||
|
|
||
|
if data.writers == 1 && !data.exclusive {
|
||
|
|
||
|
let op = libc::flock {
|
||
|
l_type: libc::F_UNLCK as i16,
|
||
|
l_whence: libc::SEEK_SET as i16,
|
||
|
l_start: 0,
|
||
|
l_len: 0,
|
||
|
l_pid: 0,
|
||
|
};
|
||
|
|
||
|
if let Err(err) = nix::fcntl::fcntl(data.file.as_raw_fd(), nix::fcntl::FcntlArg::F_SETLKW(&op)) {
|
||
|
panic!("unable to drop writer lock - {}", err);
|
||
|
}
|
||
|
data.writers = 0;
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
|
||
|
/// Lock guard for exclusive locks
|
||
|
///
|
||
|
/// Release the lock when it goes out of scope.
|
||
|
pub struct ProcessLockExclusiveGuard {
|
||
|
locker: Arc<Mutex<ProcessLocker>>,
|
||
|
}
|
||
|
|
||
|
impl Drop for ProcessLockExclusiveGuard {
|
||
|
fn drop(&mut self) {
|
||
|
let mut data = self.locker.lock().unwrap();
|
||
|
|
||
|
if !data.exclusive { panic!("unexpected ProcessLocker state"); }
|
||
|
|
||
|
let ltype = if data.writers != 0 { libc::F_RDLCK } else { libc::F_UNLCK };
|
||
|
let op = libc::flock {
|
||
|
l_type: ltype as i16,
|
||
|
l_whence: libc::SEEK_SET as i16,
|
||
|
l_start: 0,
|
||
|
l_len: 0,
|
||
|
l_pid: 0,
|
||
|
};
|
||
|
|
||
|
if let Err(err) = nix::fcntl::fcntl(data.file.as_raw_fd(), nix::fcntl::FcntlArg::F_SETLKW(&op)) {
|
||
|
panic!("unable to drop exclusive lock - {}", err);
|
||
|
}
|
||
|
|
||
|
data.exclusive = false;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
impl ProcessLocker {
|
||
|
|
||
|
/// Create a new instance for the specified file.
|
||
|
///
|
||
|
/// This simply creates the file if it does not exist.
|
||
|
pub fn new(lockfile: &str) -> Result<Arc<Mutex<Self>>, Error> {
|
||
|
|
||
|
let file = std::fs::OpenOptions::new()
|
||
|
.create(true)
|
||
|
.read(true)
|
||
|
.write(true)
|
||
|
.open(lockfile)?;
|
||
|
|
||
|
Ok(Arc::new(Mutex::new(Self {
|
||
|
file: file,
|
||
|
exclusive: false,
|
||
|
writers: 0,
|
||
|
})))
|
||
|
}
|
||
|
|
||
|
fn try_lock(file: &std::fs::File, ltype: i32) -> Result<(), Error> {
|
||
|
|
||
|
let op = libc::flock {
|
||
|
l_type: ltype as i16,
|
||
|
l_whence: libc::SEEK_SET as i16,
|
||
|
l_start: 0,
|
||
|
l_len: 0,
|
||
|
l_pid: 0,
|
||
|
};
|
||
|
|
||
|
nix::fcntl::fcntl(file.as_raw_fd(), nix::fcntl::FcntlArg::F_SETLK(&op))?;
|
||
|
|
||
|
Ok(())
|
||
|
}
|
||
|
|
||
|
/// Try to aquire a shared lock
|
||
|
///
|
||
|
/// On sucess, this makes sure that no other process can get an exclusive lock for the file.
|
||
|
pub fn try_shared_lock(locker: Arc<Mutex<Self>>) -> Result<ProcessLockSharedGuard, Error> {
|
||
|
|
||
|
let mut data = locker.lock().unwrap();
|
||
|
|
||
|
if data.writers == 0 && !data.exclusive {
|
||
|
if let Err(err) = Self::try_lock(&data.file, libc::F_RDLCK) {
|
||
|
bail!("unable to get shared lock - {}", err);
|
||
|
}
|
||
|
}
|
||
|
|
||
|
data.writers += 1;
|
||
|
|
||
|
Ok(ProcessLockSharedGuard { locker: locker.clone() })
|
||
|
}
|
||
|
|
||
|
/// Try to aquire a exclusive lock
|
||
|
///
|
||
|
/// Make sure the we are the only process which has locks for this file (shared or exclusive).
|
||
|
pub fn try_exclusive_lock(locker: Arc<Mutex<Self>>) -> Result<ProcessLockExclusiveGuard, Error> {
|
||
|
|
||
|
let mut data = locker.lock().unwrap();
|
||
|
|
||
|
if data.exclusive {
|
||
|
bail!("already locked exclusively");
|
||
|
}
|
||
|
|
||
|
if let Err(err) = Self::try_lock(&data.file, libc::F_WRLCK) {
|
||
|
bail!("unable to get exclusive lock - {}", err);
|
||
|
}
|
||
|
|
||
|
data.exclusive = true;
|
||
|
|
||
|
Ok(ProcessLockExclusiveGuard { locker: locker.clone() })
|
||
|
}
|
||
|
}
|