src/tools/process_locker.rs: implement inter-process reader-writer locks

This commit is contained in:
Dietmar Maurer 2019-03-22 08:04:12 +01:00
parent eb90c9e372
commit a650f50301
3 changed files with 159 additions and 1 deletions

View File

@ -29,7 +29,7 @@ hyper-tls = "0.3"
lazy_static = "1.1" lazy_static = "1.1"
regex = "1.0" regex = "1.0"
libc = "0.2" libc = "0.2"
nix = "0.12" nix = "0.13"
shellwords = "1.0" shellwords = "1.0"
uuid = { version = "0.7", features = ["v4"] } uuid = { version = "0.7", features = ["v4"] }
chrono = "0.4" # Date and time library for Rust chrono = "0.4" # Date and time library for Rust

View File

@ -32,6 +32,9 @@ pub mod tty;
pub mod signalfd; pub mod signalfd;
pub mod daemon; pub mod daemon;
mod process_locker;
pub use process_locker::*;
#[macro_use] #[macro_use]
mod file_logger; mod file_logger;
pub use file_logger::*; pub use file_logger::*;

155
src/tools/process_locker.rs Normal file
View File

@ -0,0 +1,155 @@
//! 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() })
}
}