src/tools/process_locker.rs: implement inter-process reader-writer locks
This commit is contained in:
		@ -32,6 +32,9 @@ pub mod tty;
 | 
			
		||||
pub mod signalfd;
 | 
			
		||||
pub mod daemon;
 | 
			
		||||
 | 
			
		||||
mod process_locker;
 | 
			
		||||
pub use process_locker::*;
 | 
			
		||||
 | 
			
		||||
#[macro_use]
 | 
			
		||||
mod file_logger;
 | 
			
		||||
pub use file_logger::*;
 | 
			
		||||
 | 
			
		||||
							
								
								
									
										155
									
								
								src/tools/process_locker.rs
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										155
									
								
								src/tools/process_locker.rs
									
									
									
									
									
										Normal 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() })
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
		Reference in New Issue
	
	Block a user