Skip to main content

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}