Skip to main content

ksync/
brwlock.rs

1// Copyright 2026 The Fuchsia Authors. All rights reserved.
2// Use of this source code is governed by a BSD-style license that can be
3// found in the LICENSE file.
4
5use core::marker::PhantomData;
6use core::pin::Pin;
7use pin_init::{PinInit, pin_data, pin_init, pinned_drop};
8
9use crate::{LockEntryStorage, RawBrwLockPi};
10use lockdep::LockClass;
11
12/// A priority-inheriting reader-writer lock.
13#[repr(transparent)]
14#[pin_data]
15pub struct BrwLockPi<Class: LockClass> {
16    #[pin]
17    lock: RawBrwLockPi,
18    _marker: PhantomData<Class>,
19}
20
21// SAFETY: BrwLockPi is safe to share across threads because the underlying RawBrwLockPi is Sync.
22unsafe impl<Class: LockClass> Sync for BrwLockPi<Class> {}
23unsafe impl<Class: LockClass> Send for BrwLockPi<Class> {}
24
25impl<Class: LockClass> BrwLockPi<Class> {
26    /// Safe dynamic initialization inside pin context.
27    pub fn init() -> impl PinInit<Self, core::convert::Infallible> {
28        pin_init!(Self {
29            // SAFETY: `Class::ID` is either null or a valid, static `LockClassId`.
30            lock <- unsafe { RawBrwLockPi::init(Class::ID) },
31            _marker: PhantomData,
32        })
33    }
34
35    /// Acquire the read lock and return a stack-pinned validation guard.
36    #[inline]
37    pub fn read_lock(
38        &self,
39    ) -> impl PinInit<BrwLockPiReadGuard<'_, Class>, core::convert::Infallible> {
40        BrwLockPiReadGuard::new(self)
41    }
42
43    /// Acquire the write lock and return a stack-pinned validation guard.
44    #[inline]
45    pub fn write_lock(
46        &self,
47    ) -> impl PinInit<BrwLockPiWriteGuard<'_, Class>, core::convert::Infallible> {
48        BrwLockPiWriteGuard::new(self)
49    }
50}
51
52/// A validation guard representing reader lock ownership and active list participation.
53#[repr(C)]
54#[pin_data(PinnedDrop)]
55pub struct BrwLockPiReadGuard<'a, Class: LockClass> {
56    lock: &'a BrwLockPi<Class>,
57    #[pin]
58    lock_entry: LockEntryStorage,
59    token: crate::LockToken<'a, Class>,
60}
61
62impl<'a, Class: LockClass> BrwLockPiReadGuard<'a, Class> {
63    /// Creates a new stack-pinned validation guard initialization block.
64    pub fn new(lock: &'a BrwLockPi<Class>) -> impl PinInit<Self, core::convert::Infallible> {
65        // SAFETY: The closure correctly initializes all fields of the allocated
66        // `BrwLockPiReadGuard` and satisfies all safety requirements of `pin_init_from_closure`.
67        unsafe {
68            pin_init::pin_init_from_closure(
69                move |this: *mut Self| -> Result<(), core::convert::Infallible> {
70                    let lock_addr = core::ptr::addr_of_mut!((*this).lock);
71                    core::ptr::write(lock_addr, lock);
72
73                    let entry_addr = core::ptr::addr_of_mut!((*this).lock_entry);
74                    core::ptr::write(entry_addr, LockEntryStorage::default());
75
76                    lock.lock.acquire_read(entry_addr as *mut core::ffi::c_void);
77
78                    let token_addr = core::ptr::addr_of_mut!((*this).token);
79                    core::ptr::write(token_addr, crate::LockToken::new());
80
81                    Ok(())
82                },
83            )
84        }
85    }
86
87    /// Returns a shared reference to the lock proof `LockToken`.
88    #[inline]
89    pub fn token(&self) -> &crate::LockToken<'a, Class> {
90        &self.token
91    }
92
93    /// Returns a mutable reference to the lock proof `LockToken` inside this pinned projection.
94    #[inline]
95    pub fn token_mut(self: Pin<&mut Self>) -> &mut crate::LockToken<'a, Class> {
96        // SAFETY: We are accessing `token` mutably but `LockToken` is a ZST and does not require
97        // pinning invariants to be maintained.
98        let me = unsafe { self.get_unchecked_mut() };
99        &mut me.token
100    }
101}
102
103#[pinned_drop]
104impl<'a, Class: LockClass> PinnedDrop for BrwLockPiReadGuard<'a, Class> {
105    fn drop(self: Pin<&mut Self>) {
106        // SAFETY: `get_unchecked_mut` is safe because we do not move the fields out of Pin.
107        // `release_read` is safe because the read lock was acquired when creating this guard,
108        // and we are releasing it with the same entry storage.
109        unsafe {
110            let me = self.get_unchecked_mut();
111            let entry_addr = &mut me.lock_entry as *mut _;
112            me.lock.lock.release_read(entry_addr as *mut core::ffi::c_void);
113        }
114    }
115}
116
117/// A validation guard representing writer lock ownership and active list participation.
118#[repr(C)]
119#[pin_data(PinnedDrop)]
120pub struct BrwLockPiWriteGuard<'a, Class: LockClass> {
121    lock: &'a BrwLockPi<Class>,
122    #[pin]
123    lock_entry: LockEntryStorage,
124    token: crate::LockToken<'a, Class>,
125}
126
127impl<'a, Class: LockClass> BrwLockPiWriteGuard<'a, Class> {
128    /// Creates a new stack-pinned validation guard initialization block.
129    pub fn new(lock: &'a BrwLockPi<Class>) -> impl PinInit<Self, core::convert::Infallible> {
130        // SAFETY: The closure correctly initializes all fields of the allocated
131        // `BrwLockPiWriteGuard` and satisfies all safety requirements of `pin_init_from_closure`.
132        unsafe {
133            pin_init::pin_init_from_closure(
134                move |this: *mut Self| -> Result<(), core::convert::Infallible> {
135                    let lock_addr = core::ptr::addr_of_mut!((*this).lock);
136                    core::ptr::write(lock_addr, lock);
137
138                    let entry_addr = core::ptr::addr_of_mut!((*this).lock_entry);
139                    core::ptr::write(entry_addr, LockEntryStorage::default());
140
141                    lock.lock.acquire_write(entry_addr as *mut core::ffi::c_void);
142
143                    let token_addr = core::ptr::addr_of_mut!((*this).token);
144                    core::ptr::write(token_addr, crate::LockToken::new());
145
146                    Ok(())
147                },
148            )
149        }
150    }
151
152    /// Returns a shared reference to the lock proof `LockToken`.
153    #[inline]
154    pub fn token(&self) -> &crate::LockToken<'a, Class> {
155        &self.token
156    }
157
158    /// Returns a mutable reference to the lock proof `LockToken` inside this pinned projection.
159    #[inline]
160    pub fn token_mut(self: Pin<&mut Self>) -> &mut crate::LockToken<'a, Class> {
161        // SAFETY: We are accessing `token` mutably but `LockToken` is a ZST and does not require
162        // pinning invariants to be maintained.
163        let me = unsafe { self.get_unchecked_mut() };
164        &mut me.token
165    }
166}
167
168#[pinned_drop]
169impl<'a, Class: LockClass> PinnedDrop for BrwLockPiWriteGuard<'a, Class> {
170    fn drop(self: Pin<&mut Self>) {
171        // SAFETY: `get_unchecked_mut` is safe because we do not move the fields out of Pin.
172        // `release_write` is safe because the write lock was acquired when creating this guard,
173        // and we are releasing it with the same entry storage.
174        unsafe {
175            let me = self.get_unchecked_mut();
176            let entry_addr = &mut me.lock_entry as *mut _;
177            me.lock.lock.release_write(entry_addr as *mut core::ffi::c_void);
178        }
179    }
180}
181
182#[cfg(test)]
183mod tests {
184    extern crate std;
185
186    use crate::{BrwLockPi, RawBrwLockPi, guarded};
187    use pin_init::{pin_init, stack_pin_init};
188    use std::string::String;
189    use std::vec::Vec;
190
191    #[guarded]
192    struct Database {
193        #[brwlock]
194        lock: BrwLockPi,
195
196        #[guarded_by(lock)]
197        data: Vec<String>,
198
199        #[guarded_by(lock)]
200        query_count: u64,
201    }
202
203    fn read_data(db: &Database) -> usize {
204        lock!(let guard = db.read_lock());
205        let len = guard.data().len();
206
207        let fields = guard.fields();
208        let _ = fields.data.len();
209        let _ = *fields.query_count;
210
211        len
212    }
213
214    fn append_data(db: &Database, value: String) {
215        lock!(let mut guard = db.write_lock());
216        let fields = guard.as_mut().fields_mut();
217        fields.data.push(value);
218        *fields.query_count += 1;
219    }
220
221    #[test]
222    fn test_brwlock_projections() {
223        stack_pin_init!(let db = pin_init!(Database {
224            lock <- BrwLockPi::init(),
225            data: Vec::new().into(),
226            query_count: 0.into(),
227        }));
228
229        assert_eq!(read_data(&db), 0);
230        append_data(&db, String::from("hello"));
231        assert_eq!(read_data(&db), 1);
232
233        lock!(let guard = db.read_lock());
234        assert_eq!(guard.data()[0], "hello");
235        assert_eq!(*guard.query_count(), 1);
236    }
237
238    #[pin_init::pin_data]
239    struct BrwLockTest {
240        #[pin]
241        lock: RawBrwLockPi,
242        state: std::sync::atomic::AtomicU32,
243        kill: std::sync::atomic::AtomicBool,
244    }
245
246    fn run_test(readers: usize, writers: usize) {
247        use std::sync::atomic::Ordering;
248
249        stack_pin_init!(let test = pin_init!(BrwLockTest {
250            lock <- unsafe { RawBrwLockPi::init(core::ptr::null()) },
251            state: std::sync::atomic::AtomicU32::new(0).into(),
252            kill: std::sync::atomic::AtomicBool::new(false).into(),
253        }));
254
255        std::thread::scope(|s| {
256            let mut threads = std::vec::Vec::new();
257
258            for _ in 0..readers {
259                threads.push(s.spawn(|| {
260                    while !test.kill.load(Ordering::Relaxed) {
261                        // SAFETY: lock is initialized and pinned.
262                        unsafe {
263                            test.lock.acquire_read(core::ptr::null_mut());
264                        }
265                        test.state.fetch_add(1, Ordering::Relaxed);
266                        std::thread::yield_now();
267                        test.state.fetch_sub(1, Ordering::Relaxed);
268                        // SAFETY: lock is held in read mode.
269                        unsafe {
270                            test.lock.release_read(core::ptr::null_mut());
271                        }
272                        std::thread::yield_now();
273                    }
274                }));
275            }
276
277            for _ in 0..writers {
278                threads.push(s.spawn(|| {
279                    while !test.kill.load(Ordering::Relaxed) {
280                        // SAFETY: lock is initialized and pinned.
281                        unsafe {
282                            test.lock.acquire_write(core::ptr::null_mut());
283                        }
284                        test.state.fetch_add(0x10000, Ordering::Relaxed);
285                        std::thread::yield_now();
286                        test.state.fetch_sub(0x10000, Ordering::Relaxed);
287                        // SAFETY: lock is held in write mode.
288                        unsafe {
289                            test.lock.release_write(core::ptr::null_mut());
290                        }
291                        std::thread::yield_now();
292                    }
293                }));
294            }
295
296            let start = std::time::Instant::now();
297            while start.elapsed() < std::time::Duration::from_millis(300) {
298                let local_state = test.state.load(Ordering::Relaxed);
299                let num_readers = (local_state & 0xffff) as usize;
300                let num_writers = (local_state >> 16) as usize;
301
302                assert!(num_readers <= readers, "Too many readers: {}", num_readers);
303                assert!(num_writers <= 1, "Too many writers: {}", num_writers);
304                assert!(
305                    num_readers == 0 || num_writers == 0,
306                    "Both readers ({}) and writers ({}) active!",
307                    num_readers,
308                    num_writers
309                );
310
311                std::thread::yield_now();
312            }
313
314            test.kill.store(true, Ordering::SeqCst);
315        });
316    }
317
318    #[test]
319    fn test_parallel_readers() {
320        run_test(8, 0);
321    }
322
323    #[test]
324    fn test_single_writer() {
325        run_test(0, 4);
326    }
327
328    #[test]
329    fn test_readers_and_writers() {
330        run_test(4, 2);
331    }
332}