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    /// Temporarily releases the read lock before executing the given callable `f` and then
103    /// re-acquires the read lock.
104    #[inline]
105    pub fn call_unlocked<R, F: FnOnce() -> R>(self: Pin<&mut Self>, f: F) -> R {
106        // SAFETY: `lock_entry` is pinned on the stack and valid.
107        unsafe {
108            let me = self.get_unchecked_mut();
109            let entry_addr = &mut me.lock_entry as *mut _;
110            me.lock.lock.release_read(entry_addr as *mut core::ffi::c_void);
111            let result = f();
112            me.lock.lock.acquire_read(entry_addr as *mut core::ffi::c_void);
113            result
114        }
115    }
116}
117
118#[pinned_drop]
119impl<'a, Class: LockClass> PinnedDrop for BrwLockPiReadGuard<'a, Class> {
120    fn drop(self: Pin<&mut Self>) {
121        // SAFETY: `get_unchecked_mut` is safe because we do not move the fields out of Pin.
122        // `release_read` is safe because the read lock was acquired when creating this guard,
123        // and we are releasing it with the same entry storage.
124        unsafe {
125            let me = self.get_unchecked_mut();
126            let entry_addr = &mut me.lock_entry as *mut _;
127            me.lock.lock.release_read(entry_addr as *mut core::ffi::c_void);
128        }
129    }
130}
131
132/// A validation guard representing writer lock ownership and active list participation.
133#[repr(C)]
134#[pin_data(PinnedDrop)]
135pub struct BrwLockPiWriteGuard<'a, Class: LockClass> {
136    lock: &'a BrwLockPi<Class>,
137    #[pin]
138    lock_entry: LockEntryStorage,
139    token: crate::LockToken<'a, Class>,
140}
141
142impl<'a, Class: LockClass> BrwLockPiWriteGuard<'a, Class> {
143    /// Creates a new stack-pinned validation guard initialization block.
144    pub fn new(lock: &'a BrwLockPi<Class>) -> impl PinInit<Self, core::convert::Infallible> {
145        // SAFETY: The closure correctly initializes all fields of the allocated
146        // `BrwLockPiWriteGuard` and satisfies all safety requirements of `pin_init_from_closure`.
147        unsafe {
148            pin_init::pin_init_from_closure(
149                move |this: *mut Self| -> Result<(), core::convert::Infallible> {
150                    let lock_addr = core::ptr::addr_of_mut!((*this).lock);
151                    core::ptr::write(lock_addr, lock);
152
153                    let entry_addr = core::ptr::addr_of_mut!((*this).lock_entry);
154                    core::ptr::write(entry_addr, LockEntryStorage::default());
155
156                    lock.lock.acquire_write(entry_addr as *mut core::ffi::c_void);
157
158                    let token_addr = core::ptr::addr_of_mut!((*this).token);
159                    core::ptr::write(token_addr, crate::LockToken::new());
160
161                    Ok(())
162                },
163            )
164        }
165    }
166
167    /// Returns a shared reference to the lock proof `LockToken`.
168    #[inline]
169    pub fn token(&self) -> &crate::LockToken<'a, Class> {
170        &self.token
171    }
172
173    /// Returns a mutable reference to the lock proof `LockToken` inside this pinned projection.
174    #[inline]
175    pub fn token_mut(self: Pin<&mut Self>) -> &mut crate::LockToken<'a, Class> {
176        // SAFETY: We are accessing `token` mutably but `LockToken` is a ZST and does not require
177        // pinning invariants to be maintained.
178        let me = unsafe { self.get_unchecked_mut() };
179        &mut me.token
180    }
181
182    /// Temporarily releases the write lock before executing the given callable `f` and then
183    /// re-acquires the write lock.
184    #[inline]
185    pub fn call_unlocked<R, F: FnOnce() -> R>(self: Pin<&mut Self>, f: F) -> R {
186        // SAFETY: `lock_entry` is pinned on the stack and valid.
187        unsafe {
188            let me = self.get_unchecked_mut();
189            let entry_addr = &mut me.lock_entry as *mut _;
190            me.lock.lock.release_write(entry_addr as *mut core::ffi::c_void);
191            let result = f();
192            me.lock.lock.acquire_write(entry_addr as *mut core::ffi::c_void);
193            result
194        }
195    }
196}
197
198#[pinned_drop]
199impl<'a, Class: LockClass> PinnedDrop for BrwLockPiWriteGuard<'a, Class> {
200    fn drop(self: Pin<&mut Self>) {
201        // SAFETY: `get_unchecked_mut` is safe because we do not move the fields out of Pin.
202        // `release_write` is safe because the write lock was acquired when creating this guard,
203        // and we are releasing it with the same entry storage.
204        unsafe {
205            let me = self.get_unchecked_mut();
206            let entry_addr = &mut me.lock_entry as *mut _;
207            me.lock.lock.release_write(entry_addr as *mut core::ffi::c_void);
208        }
209    }
210}
211
212#[cfg(test)]
213mod tests {
214    extern crate std;
215
216    use crate::{BrwLockPi, RawBrwLockPi, guarded};
217    use pin_init::{pin_init, stack_pin_init};
218    use std::string::String;
219    use std::vec::Vec;
220
221    #[guarded]
222    struct Database {
223        #[brwlock]
224        lock: BrwLockPi,
225
226        #[guarded_by(lock)]
227        data: Vec<String>,
228
229        #[guarded_by(lock)]
230        query_count: u64,
231    }
232
233    fn read_data(db: &Database) -> usize {
234        lock!(let guard = db.read_lock());
235        let len = guard.data().len();
236
237        let fields = guard.fields();
238        let _ = fields.data.len();
239        let _ = *fields.query_count;
240
241        len
242    }
243
244    fn append_data(db: &Database, value: String) {
245        lock!(let mut guard = db.write_lock());
246        let fields = guard.as_mut().fields_mut();
247        fields.data.push(value);
248        *fields.query_count += 1;
249    }
250
251    #[test]
252    fn test_brwlock_projections() {
253        stack_pin_init!(let db = pin_init!(Database {
254            lock <- BrwLockPi::init(),
255            data: Vec::new().into(),
256            query_count: 0.into(),
257        }));
258
259        assert_eq!(read_data(&db), 0);
260        append_data(&db, String::from("hello"));
261        assert_eq!(read_data(&db), 1);
262
263        lock!(let guard = db.read_lock());
264        assert_eq!(guard.data()[0], "hello");
265        assert_eq!(*guard.query_count(), 1);
266    }
267
268    #[pin_init::pin_data]
269    struct BrwLockTest {
270        #[pin]
271        lock: RawBrwLockPi,
272        state: std::sync::atomic::AtomicU32,
273        kill: std::sync::atomic::AtomicBool,
274    }
275
276    fn run_test(readers: usize, writers: usize) {
277        use std::sync::atomic::Ordering;
278
279        stack_pin_init!(let test = pin_init!(BrwLockTest {
280            lock <- unsafe { RawBrwLockPi::init(core::ptr::null()) },
281            state: std::sync::atomic::AtomicU32::new(0).into(),
282            kill: std::sync::atomic::AtomicBool::new(false).into(),
283        }));
284
285        std::thread::scope(|s| {
286            let mut threads = std::vec::Vec::new();
287
288            for _ in 0..readers {
289                threads.push(s.spawn(|| {
290                    while !test.kill.load(Ordering::Relaxed) {
291                        // SAFETY: lock is initialized and pinned.
292                        unsafe {
293                            test.lock.acquire_read(core::ptr::null_mut());
294                        }
295                        test.state.fetch_add(1, Ordering::Relaxed);
296                        std::thread::yield_now();
297                        test.state.fetch_sub(1, Ordering::Relaxed);
298                        // SAFETY: lock is held in read mode.
299                        unsafe {
300                            test.lock.release_read(core::ptr::null_mut());
301                        }
302                        std::thread::yield_now();
303                    }
304                }));
305            }
306
307            for _ in 0..writers {
308                threads.push(s.spawn(|| {
309                    while !test.kill.load(Ordering::Relaxed) {
310                        // SAFETY: lock is initialized and pinned.
311                        unsafe {
312                            test.lock.acquire_write(core::ptr::null_mut());
313                        }
314                        test.state.fetch_add(0x10000, Ordering::Relaxed);
315                        std::thread::yield_now();
316                        test.state.fetch_sub(0x10000, Ordering::Relaxed);
317                        // SAFETY: lock is held in write mode.
318                        unsafe {
319                            test.lock.release_write(core::ptr::null_mut());
320                        }
321                        std::thread::yield_now();
322                    }
323                }));
324            }
325
326            let start = std::time::Instant::now();
327            while start.elapsed() < std::time::Duration::from_millis(300) {
328                let local_state = test.state.load(Ordering::Relaxed);
329                let num_readers = (local_state & 0xffff) as usize;
330                let num_writers = (local_state >> 16) as usize;
331
332                assert!(num_readers <= readers, "Too many readers: {}", num_readers);
333                assert!(num_writers <= 1, "Too many writers: {}", num_writers);
334                assert!(
335                    num_readers == 0 || num_writers == 0,
336                    "Both readers ({}) and writers ({}) active!",
337                    num_readers,
338                    num_writers
339                );
340
341                std::thread::yield_now();
342            }
343
344            test.kill.store(true, Ordering::SeqCst);
345        });
346    }
347
348    #[test]
349    fn test_parallel_readers() {
350        run_test(8, 0);
351    }
352
353    #[test]
354    fn test_single_writer() {
355        run_test(0, 4);
356    }
357
358    #[test]
359    fn test_readers_and_writers() {
360        run_test(4, 2);
361    }
362
363    #[allow(dead_code)]
364    struct CustomBrwLockClass;
365    impl lockdep::LockClass for CustomBrwLockClass {
366        const ID: *mut core::ffi::c_void = core::ptr::null_mut();
367    }
368
369    #[guarded]
370    struct CustomBrwLockStruct {
371        #[brwlock(CustomBrwLockClass)]
372        lock: BrwLockPi<CustomBrwLockClass>,
373        #[guarded_by(lock)]
374        value: u32,
375    }
376
377    #[test]
378    fn test_custom_brwlock_class() {
379        stack_pin_init!(let s = pin_init!(CustomBrwLockStruct {
380            lock <- BrwLockPi::init(),
381            value: 42.into(),
382        }));
383
384        lock!(let guard = s.read_lock());
385        assert_eq!(*guard.value(), 42);
386    }
387}