vpp_plugin/vlib/main/sync.rs
1//! Core synchronization primitives for vlib main.
2
3use std::{
4 cell::{Cell, UnsafeCell},
5 fmt,
6 marker::PhantomData,
7 ops::{Deref, DerefMut},
8 ptr::NonNull,
9};
10
11use crate::vlib::{BarrierHeldMainRef, MainRef};
12
13/// A read/write lock using VPP's barrier to provide exclusion between threads
14///
15/// VPP implements a barrier in the main thread which blocks all worker threads from running. The
16/// `BarrierRwLock` is an abstraction around this which allows a writer in the VPP main thread
17/// whilst the barrier is held and readers in either VPP workers or the VPP main thread.
18///
19/// Taking read or write "locks" are guaranteed to never block - blocking instead occurs in the
20/// VPP main and worker threads when the VPP barrier is taken.
21pub struct BarrierRwLock<T: ?Sized> {
22 /// The number of readers
23 ///
24 /// Note that this doesn't use `AtomicU32` because it's only modified on the VPP main thread.
25 readers: UnsafeCell<u32>,
26 /// Whether there is a writer
27 ///
28 /// Note that this doesn't use `AtomicBool` because it's only modified on the VPP main thread.
29 writer: UnsafeCell<bool>,
30 /// The lock-protected data.
31 data: UnsafeCell<T>,
32}
33
34impl<T> BarrierRwLock<T> {
35 /// Create a new barrier-backed read/write lock.
36 #[inline]
37 pub const fn new(t: T) -> Self {
38 Self {
39 data: UnsafeCell::new(t),
40 readers: UnsafeCell::new(0),
41 writer: UnsafeCell::new(false),
42 }
43 }
44}
45
46impl<T: ?Sized> BarrierRwLock<T> {
47 /// Locks this `BarrierRwLock` with shared read access.
48 ///
49 /// Returns an RAII guard which will release this thread's shared access
50 /// once it is dropped.
51 ///
52 /// # Panics
53 ///
54 /// Panics if a write lock has already been taken by this thread and not dropped.
55 /// ```
56 #[inline(always)]
57 pub fn read(&self, vm: &MainRef) -> BarrierRwLockReadGuard<'_, T> {
58 let main_thread = vm.thread_index() == 0;
59 // SAFETY: calling `BarrierRwLockReadGuard::new` is valid when we have a reference to the lock
60 // and we are on a known VPP thread. These conditions are satisfied by the public API.
61 unsafe { BarrierRwLockReadGuard::new(self, main_thread) }
62 }
63
64 /// Locks this `BarrierRwLock` with write access.
65 ///
66 /// This is used on the VPP main thread in contexts where the VPP barrier is held.
67 ///
68 /// Returns an RAII guard which will release this thread's access
69 /// once it is dropped.
70 ///
71 /// # Panics
72 ///
73 /// Panics if a read or another write lock has already been taken by this thread and not
74 /// dropped.
75 /// ```
76 #[inline(always)]
77 pub fn write(&self, vm: &BarrierHeldMainRef) -> BarrierRwLockWriteGuard<'_, T> {
78 // Make sure we match the check in read()
79 debug_assert_eq!(vm.thread_index(), 0);
80 // SAFETY: `BarrierRwLockWriteGuard::new` is only called on the main thread while the
81 // barrier is held.
82 unsafe { BarrierRwLockWriteGuard::new(self) }
83 }
84
85 /// Get a mutable reference to the contained data without locking.
86 ///
87 /// This call borrows the `BarrierRwLock` mutably (at compile-time) which guarantees that we
88 /// possess the only reference.
89 pub fn get_mut(&mut self) -> &mut T {
90 self.data.get_mut()
91 }
92
93 /// Returns a raw pointer to the underlying data.
94 ///
95 /// The returned pointer is always non-null and properly aligned, but it is
96 /// the user's responsibility to ensure that any reads and writes through it
97 /// are properly synchronized to avoid data races, and that it is not read
98 /// or written through after the lock is dropped.
99 pub const fn data_ptr(&self) -> *mut T {
100 self.data.get()
101 }
102}
103
104impl<T> BarrierRwLock<T> {
105 /// Consume the lock and return the underlying data.
106 pub fn into_inner(self) -> T {
107 self.data.into_inner()
108 }
109}
110
111// SAFETY: `BarrierRwLock<T>` is safe to send to another thread if `T: Send`.
112unsafe impl<T: ?Sized + Send> Send for BarrierRwLock<T> {}
113
114// SAFETY: `BarrierRwLock<T>` is safe to share between threads if `T: Send + Sync`.
115unsafe impl<T: ?Sized + Send + Sync> Sync for BarrierRwLock<T> {}
116
117impl<T: Default> Default for BarrierRwLock<T> {
118 /// Creates a new `BarrierRwLock<T>`, with the `Default` value for T.
119 fn default() -> BarrierRwLock<T> {
120 BarrierRwLock::new(Default::default())
121 }
122}
123
124/// Shared read guard returned by [`BarrierRwLock::read`].
125pub struct BarrierRwLockReadGuard<'rwlock, T: ?Sized + 'rwlock> {
126 /// A pointer to the data protected by the `BarrierRwLock`. Note that we use a pointer here
127 /// instead of `&'rwlock T` to avoid `noalias` violations, because a `BarrierRwLockReadGuard`
128 /// instance only holds immutability until it drops, not for its whole scope.
129 data: NonNull<T>,
130
131 /// A reference to the [`BarrierRwLock`] that we have read-locked.
132 lock: &'rwlock BarrierRwLock<T>,
133
134 /// Whether the lock is on the VPP main thread or not
135 main_thread: bool,
136}
137
138// Note: Send not implemented here as that would prevent the optimisation of not incrementing
139// readers for VPP worker threads, since the guard could then be sent to the VPP main thread
140// and used to access data while there is a write lock taken, which violates `noalias` rules.
141
142// SAFETY: `BarrierRwLockReadGuard` is immutable references to valid data; `Sync` is safe for T: Sync.
143unsafe impl<T: ?Sized + Sync> Sync for BarrierRwLockReadGuard<'_, T> {}
144
145impl<'rwlock, T: ?Sized> BarrierRwLockReadGuard<'rwlock, T> {
146 /// Creates a new instance of `BarrierRwLockReadGuard<T>` from a `BarrierRwLock<T>`.
147 ///
148 /// # Panics
149 ///
150 /// Panics if a write lock has already been taken by this thread and not dropped.
151 ///
152 /// # Safety
153 ///
154 /// This function is safe if and only if called from a thread that VPP barriers know about,
155 /// i.e. either the VPP main thread or a VPP worker thread.
156 #[inline(always)]
157 unsafe fn new(
158 lock: &'rwlock BarrierRwLock<T>,
159 main_thread: bool,
160 ) -> BarrierRwLockReadGuard<'rwlock, T> {
161 // SAFETY: `lock.writer` is valid because `lock` is a valid pointer to a live lock.
162 if main_thread && unsafe { *lock.writer.get() } {
163 panic!("Write lock already taken by this thread");
164 }
165
166 // SAFETY: `lock.data` is valid and aligned, and lock lifetime guarantees it outlives the guard.
167 let data = unsafe { NonNull::new_unchecked(lock.data.get()) };
168 if main_thread {
169 // SAFETY: Only main thread increments/decrements readers so there is no data race.
170 unsafe {
171 *lock.readers.get() += 1;
172 }
173 }
174 Self {
175 data,
176 lock,
177 main_thread,
178 }
179 }
180}
181
182impl<T: ?Sized> Drop for BarrierRwLockReadGuard<'_, T> {
183 #[inline(always)]
184 fn drop(&mut self) {
185 if self.main_thread {
186 // SAFETY: Only main thread mutates `readers` so there is no data race. We are on
187 // the main thread by conditional.
188 unsafe {
189 *self.lock.readers.get() -= 1;
190 }
191 }
192 }
193}
194
195impl<T: ?Sized> Deref for BarrierRwLockReadGuard<'_, T> {
196 type Target = T;
197
198 #[inline(always)]
199 fn deref(&self) -> &T {
200 // SAFETY: the conditions of `BarrierRwLockReadGuard::new` were satisfied when created.
201 unsafe { self.data.as_ref() }
202 }
203}
204
205impl<T: ?Sized + fmt::Debug> fmt::Debug for BarrierRwLockReadGuard<'_, T> {
206 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
207 (**self).fmt(f)
208 }
209}
210
211impl<T: ?Sized + fmt::Display> fmt::Display for BarrierRwLockReadGuard<'_, T> {
212 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
213 (**self).fmt(f)
214 }
215}
216
217/// Exclusive write guard returned by [`BarrierRwLock::write`].
218pub struct BarrierRwLockWriteGuard<'rwlock, T: ?Sized + 'rwlock> {
219 /// A reference to the [`RwLock`] that we have write-locked.
220 lock: &'rwlock BarrierRwLock<T>,
221
222 /// Prevent the type from being Send
223 _phantom: PhantomData<Cell<()>>,
224}
225
226impl<'rwlock, T: ?Sized> BarrierRwLockWriteGuard<'rwlock, T> {
227 /// Creates a new instance of `BarrierRwLockWriteGuard<T>` from a `BarrierRwLock<T>`.
228 ///
229 /// # Panics
230 ///
231 /// Panics if a read or another write lock has already been taken by this thread and not
232 /// dropped.
233 ///
234 /// # Safety
235 ///
236 /// This function is safe if and only if the same thread is holding the VPP barrier prior to
237 /// calling this function and continues to hold it for the lifetime of this object.
238 #[inline(always)]
239 unsafe fn new(lock: &'rwlock BarrierRwLock<T>) -> BarrierRwLockWriteGuard<'rwlock, T> {
240 // SAFETY: this function is only called with barrier held and no concurrent write.
241 unsafe {
242 if *lock.readers.get() != 0 {
243 panic!("Read lock already taken by this thread");
244 }
245 if *lock.writer.get() {
246 panic!("Write lock already taken by this thread");
247 }
248 *lock.writer.get() = true;
249 }
250 BarrierRwLockWriteGuard {
251 lock,
252 _phantom: PhantomData,
253 }
254 }
255}
256
257impl<T: ?Sized> Drop for BarrierRwLockWriteGuard<'_, T> {
258 #[inline(always)]
259 fn drop(&mut self) {
260 // SAFETY: This is the only writer and barrier is held while the guard is alive.
261 unsafe {
262 *self.lock.writer.get() = false;
263 }
264 }
265}
266
267// Note: no Send implementation as it's not safe to modify `self.lock.writer` on Drop and sending
268// the write guard across threads has limited usefulness.
269
270// SAFETY: `BarrierRwLockWriteGuard` provides exclusive write access and is Sync for `T: Sync`.
271unsafe impl<T: ?Sized + Sync> Sync for BarrierRwLockWriteGuard<'_, T> {}
272
273impl<T: ?Sized> Deref for BarrierRwLockWriteGuard<'_, T> {
274 type Target = T;
275
276 #[inline(always)]
277 fn deref(&self) -> &T {
278 // SAFETY: the conditions of `BarrierRwLockWriteGuard::new` were satisfied when created.
279 unsafe { &*self.lock.data.get() }
280 }
281}
282
283impl<T: ?Sized> DerefMut for BarrierRwLockWriteGuard<'_, T> {
284 #[inline(always)]
285 fn deref_mut(&mut self) -> &mut T {
286 // SAFETY: the conditions of `BarrierRwLockWriteGuard::new` were satisfied when created.
287 unsafe { &mut *self.lock.data.get() }
288 }
289}
290
291impl<T: ?Sized + fmt::Debug> fmt::Debug for BarrierRwLockWriteGuard<'_, T> {
292 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
293 (**self).fmt(f)
294 }
295}
296
297impl<T: ?Sized + fmt::Display> fmt::Display for BarrierRwLockWriteGuard<'_, T> {
298 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
299 (**self).fmt(f)
300 }
301}
302
303#[cfg(test)]
304mod tests {
305 use std::thread;
306
307 use crate::{
308 bindings::vlib_main_t,
309 vlib::{BarrierHeldMainRef, MainRef, main::sync::BarrierRwLock},
310 };
311
312 #[test]
313 fn concurrent_reads() {
314 let lock = BarrierRwLock::new("value".to_string());
315 let ref_lock = &lock;
316 thread::scope(|s| {
317 let thread1 = s.spawn(move || {
318 let mut main = vlib_main_t::default();
319 // SAFETY: main is sufficiently initialised for the test and valid for the duration of the
320 // call.
321 let main_ref = unsafe { MainRef::from_ptr_mut(std::ptr::addr_of_mut!(main)) };
322 for _ in 0..1000 {
323 assert_eq!(*ref_lock.read(main_ref), "value");
324 }
325 });
326 let thread2 = s.spawn(move || {
327 let mut main = vlib_main_t {
328 thread_index: 1,
329 ..vlib_main_t::default()
330 };
331 // SAFETY: main is sufficiently initialised for the test and valid for the duration of the
332 // call.
333 let main_ref = unsafe { MainRef::from_ptr_mut(std::ptr::addr_of_mut!(main)) };
334 for _ in 0..1000 {
335 assert_eq!(*ref_lock.read(main_ref), "value");
336 }
337 });
338 thread1.join().unwrap();
339 thread2.join().unwrap();
340 });
341 }
342
343 #[test]
344 fn write_guard() {
345 let mut main = vlib_main_t::default();
346 // SAFETY: main is sufficiently initialised for the test and valid for the duration of the
347 // call.
348 let main_ref = unsafe { BarrierHeldMainRef::from_ptr_mut(std::ptr::addr_of_mut!(main)) };
349 let lock = BarrierRwLock::new("value".to_string());
350 *lock.write(main_ref) = "new value".to_string();
351 assert_eq!(*lock.read(main_ref), "new value");
352 }
353
354 #[test]
355 #[should_panic(expected = "Write lock already taken by this thread")]
356 fn read_and_write1() {
357 let mut main = vlib_main_t::default();
358 // SAFETY: main is sufficiently initialised for the test and valid for the duration of the
359 // call.
360 let main_ref = unsafe { BarrierHeldMainRef::from_ptr_mut(std::ptr::addr_of_mut!(main)) };
361 let lock = BarrierRwLock::new("value".to_string());
362 let _guard1 = lock.write(main_ref);
363 let _guard2 = lock.read(main_ref);
364 }
365
366 #[test]
367 #[should_panic(expected = "Read lock already taken by this thread")]
368 fn read_and_write2() {
369 let mut main = vlib_main_t::default();
370 // SAFETY: main is sufficiently initialised for the test and valid for the duration of the
371 // call.
372 let main_ref = unsafe { BarrierHeldMainRef::from_ptr_mut(std::ptr::addr_of_mut!(main)) };
373 let lock = BarrierRwLock::new("value".to_string());
374 let _guard1 = lock.read(main_ref);
375 let _guard2 = lock.write(main_ref);
376 }
377
378 #[test]
379 #[should_panic(expected = "Write lock already taken by this thread")]
380 fn write_write() {
381 let mut main = vlib_main_t::default();
382 // SAFETY: main is sufficiently initialised for the test and valid for the duration of the
383 // call.
384 let main_ref = unsafe { BarrierHeldMainRef::from_ptr_mut(std::ptr::addr_of_mut!(main)) };
385 let lock = BarrierRwLock::new("value".to_string());
386 let _guard1 = lock.write(main_ref);
387 let _guard2 = lock.write(main_ref);
388 }
389
390 /// Test misc small utilities of [`BarrierRwLock`]
391 #[test]
392 fn misc() {
393 let mut main = vlib_main_t::default();
394 // SAFETY: main is sufficiently initialised for the test and valid for the duration of the
395 // call.
396 let main_ref = unsafe { BarrierHeldMainRef::from_ptr_mut(std::ptr::addr_of_mut!(main)) };
397 let mut lock: BarrierRwLock<String> = BarrierRwLock::default();
398
399 assert_eq!(*lock.write(main_ref), "");
400
401 *lock.get_mut() = "value".to_string();
402
403 assert_eq!(lock.write(main_ref).to_string(), "value");
404 assert_eq!(format!("{:?}", lock.write(main_ref)), "\"value\"");
405 assert_eq!(lock.read(main_ref).to_string(), "value");
406 assert_eq!(format!("{:?}", lock.read(main_ref)), "\"value\"");
407
408 // SAFETY: data_ptr() returns a valid pointer and it remains valid throughout its use
409 unsafe {
410 assert_eq!(&*lock.data_ptr(), "value");
411 }
412
413 assert_eq!(lock.into_inner(), "value");
414 }
415}