mmap_sync/locks.rs
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165
//! Lock strategies.
//!
//! # Safety
//! This crate requires that only one writer be active at a time. It is the caller's responsibility
//! to uphold th is guarantee.
//!
//! Note: if multiple writers are active, it is the caller's responsibility to ensure that each
//! writer is configured with an appropriate lock strategy. For example, mixing the `LockDisabled`
//! strategy with any other strategy is incorrect because it disables lock checks from one of the
//! synchronizers.
#[cfg(unix)]
use std::os::fd::AsRawFd;
use std::{
fs::File,
ops::{Deref, DerefMut},
};
use memmap2::MmapMut;
use crate::synchronizer::SynchronizerError;
/// The write lock strategy supports different lock implementations which can be chosen based on
/// the guarantees required, platform support, and performance constraints.
///
/// Note: the lock implementations are sealed to avoid committing to a specific lock interface.
#[allow(private_bounds)]
pub trait WriteLockStrategy<'a>: WriteLockStrategySealed<'a> {}
/// Sealed trait i
pub(crate) trait WriteLockStrategySealed<'a> {
type Guard: DerefMut<Target = MmapMut> + 'a;
/// Create a new instance of this lock strategy.
///
/// The `mmap` parameter will have write access controlled by the lock.
///
/// The `file` parameter is required because lock strategies depending on `flock` must hold
/// the file descriptor open so the kernel does not release the lock.
fn new(mmap: MmapMut, file: File) -> Self;
/// Provide read access to mmaped memory.
fn read(&'a self) -> &'a [u8];
/// Acquire the lock as specified by the lock strategy.
///
/// On success, return a lock guard which can be used to access the underlying mmaped memory
/// via [`Deref`]/[`DerefMut`].
fn lock(&'a mut self) -> Result<Self::Guard, SynchronizerError>;
}
/// Lock protection is disabled.
///
/// # Safety
/// Callers must ensure that there is only a single active writer. For example, the caller might
/// ensure that only one process attempts to write to the synchronizer, and ensure that multiple
/// instances of the process are not spawned.
pub struct LockDisabled(MmapMut);
impl<'a> WriteLockStrategySealed<'a> for LockDisabled {
type Guard = DisabledGuard<'a>;
#[inline]
fn new(mmap: MmapMut, _file: File) -> Self {
// No need to hold the file descriptor because lock functionality is disabled.
Self(mmap)
}
#[inline]
fn read(&'a self) -> &'a [u8] {
&self.0
}
#[inline]
fn lock(&'a mut self) -> Result<Self::Guard, SynchronizerError> {
Ok(DisabledGuard(&mut self.0))
}
}
impl<'a> WriteLockStrategy<'a> for LockDisabled {}
pub struct DisabledGuard<'a>(&'a mut MmapMut);
impl<'a> Deref for DisabledGuard<'a> {
type Target = MmapMut;
fn deref(&self) -> &Self::Target {
&*self.0
}
}
impl<'a> DerefMut for DisabledGuard<'a> {
fn deref_mut(&mut self) -> &mut Self::Target {
&mut *self.0
}
}
/// Acquire the lock. Once acquired, hold the lock until dropped.
///
/// The `flock` API holds the lock as long as the file descriptor is open, and closes the lock
/// when the descriptor is closed. The descriptor is automatically closed when `File` is dropped.
#[cfg(unix)]
pub struct SingleWriter {
mmap: MmapMut,
file: File,
locked: bool,
}
#[cfg(unix)]
impl<'a> WriteLockStrategySealed<'a> for SingleWriter {
type Guard = SingleWriterGuard<'a>;
#[inline]
fn new(mmap: MmapMut, file: File) -> Self {
Self {
mmap,
file,
locked: false,
}
}
#[inline]
fn read(&'a self) -> &'a [u8] {
&self.mmap
}
#[inline]
fn lock(&'a mut self) -> Result<Self::Guard, SynchronizerError> {
// We already hold the lock, so return success.
if self.locked {
return Ok(SingleWriterGuard(&mut self.mmap));
}
// Acquire the lock for the first time.
// Note: the file descriptor must remain open to hold the lock.
match unsafe { libc::flock(self.file.as_raw_fd(), libc::LOCK_EX | libc::LOCK_NB) } {
0 => {
// Hold the lock until this structure is dropped.
self.locked = true;
Ok(SingleWriterGuard(&mut self.mmap))
}
_ => Err(SynchronizerError::WriteLockConflict),
}
}
}
impl<'a> WriteLockStrategy<'a> for SingleWriter {}
/// A simple guard which does not release the lock upon being dropped.
#[cfg(unix)]
pub struct SingleWriterGuard<'a>(&'a mut MmapMut);
impl<'a> Deref for SingleWriterGuard<'a> {
type Target = MmapMut;
fn deref(&self) -> &Self::Target {
&*self.0
}
}
impl<'a> DerefMut for SingleWriterGuard<'a> {
fn deref_mut(&mut self) -> &mut Self::Target {
&mut *self.0
}
}