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}