mmap_sync/
locks.rs

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