Skip to main content

starnix_sync/
lock_sequence.rs

1// Copyright 2023 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
5//! Tools for describing and enforcing lock acquisition order.
6//!
7//! To use these tools:
8//! 1. A lock level must be defined for each type of lock. This can be a simple enum.
9//! 2. Then a relation `LockedAfter` between these levels must be described,
10//! forming a graph. This graph must be acyclic, since a cycle would indicate
11//! a potential deadlock.
12//! 3. Each time a lock is acquired, it must be done using an object of a `Locked<P>`
13//! type, where `P` is any lock level that comes before the level `L` that is
14//! associated with this lock. Doing so yields a new object of type `Locked<L>`
15//! that can be used to acquire subsequent locks.
16//! 3. Each place where a lock is used must be marked with the maximum lock level
17//! that can be already acquired before attempting to acquire this lock. To do this,
18//! it takes a special marker object `Locked<P>` where `P` is a lock level that
19//! must come before the level associated in this lock in the graph. This object
20//! is then used to acquire the lock, and a new object `Locked<L>` is returned, with
21//! a new lock level `L` that comes after `P` in the lock ordering graph.
22//!
23//! ## Example
24//! See also tests for this crate.
25//!
26//! ```
27//! use fuchsia_sync::Mutex;
28//! use starnix_sync::{lock_ordering, lock::LockFor, relation::LockAfter, Unlocked};
29//!
30//! #[derive(Default)]
31//! struct HoldsLocks {
32//!    a: Mutex<u8>,
33//!    b: Mutex<u32>,
34//! }
35//!
36//! lock_ordering! {
37//!    // LockA is the top of the lock hierarchy.
38//!    Unlocked => LevelA,
39//!    // LockA can be acquired before LockB.
40//!    LevelA => LevelB,
41//! }
42//!
43//! impl LockFor<LockA> for HoldsLocks {
44//!    type Data = u8;
45//!    type Guard<'l> = fuchsia_sync::MutexGuard<'l, u8>
46//!        where Self: 'l;
47//!    fn lock(&self) -> Self::Guard<'_> {
48//!        self.a.lock()
49//!    }
50//! }
51//!
52//! impl LockFor<LockB> for HoldsLocks {
53//!    type Data = u32;
54//!    type Guard<'l> = fuchsia_sync::MutexGuard<'l, u32>
55//!        where Self: 'l;
56//!    fn lock(&self) -> Self::Guard<'_> {
57//!        self.b.lock()
58//!    }
59//! }
60//!
61//! // Accessing locked state looks like this:
62//!
63//! let state = HoldsLocks::default();
64//! // Create a new lock session with the "root" lock level (empty tuple).
65//! let locked = Unlocked::new();
66//! // Access locked state.
67//! let (a, locked_a) = locked.lock_and::<LockA, _>(&state);
68//! let b = locked_a.lock::<LockB, _>(&state);
69//! ```
70//!
71//! The [lock_ordering] macro provides definitions for lock levels and
72//! implementations of [LockAfter] for all the locks that are connected
73//! in the graph (one can be locked after another). It also prevents
74//! accidental lock ordering inversion introduced while defining the graph
75//! by detecting cycles in it.
76//!
77//! This won't compile:
78//! ```compile_fail
79//! lock_ordering!{
80//!     Unlocked => A,
81//!     A => B,
82//!     B => A,
83//! }
84//! ```
85//!
86//! The methods on [Locked] prevent out-of-order locking according to the
87//! specified lock relationships.
88//!
89//! This won't compile because `LockB` does not implement `LockBefore<LockA>`:
90//! ```compile_fail
91//! # use fuchsia_sync::Mutex;
92//! # use starnix_sync::{lock_ordering, lock::LockFor, Locked, Unlocked};
93//! #
94//! # #[derive(Default)]
95//! # struct HoldsLocks {
96//! #    a: Mutex<u8>,
97//! #    b: Mutex<u32>,
98//! # }
99//! #
100//! # lock_ordering! {
101//! #    // LockA is the top of the lock hierarchy.
102//! #    Unlocked => LockA,
103//! #    // LockA can be acquired before LockB.
104//! #    LockA => LockB,
105//! # }
106//! #
107//! # impl LockFor<LockA> for HoldsLocks {
108//! #    type Data = u8;
109//! #    type Guard<'l> = fuchsia_sync::MutexGuard<'l, u8>
110//! #        where Self: 'l;
111//! #    fn lock(&self) -> Self::Guard<'_> {
112//! #        self.a.lock().unwrap()
113//! #    }
114//! # }
115//! #
116//! # impl LockFor<LockB> for HoldsLocks {
117//! #     type Data = u32;
118//! #     type Guard<'l> = fuchsia_sync::MutexGuard<'l, u32>
119//! #         where Self: 'l;
120//! #     fn lock(&self) -> Self::Guard<'_> {
121//! #         self.b.lock().unwrap()
122//! #     }
123//! # }
124//! #
125//!
126//! let state = HoldsLocks::default();
127//! let locked = Unlocked::new();
128//!
129//! // Locking B without A is fine, but locking A after B is not.
130//! let (b, locked_b) = locked.lock_and::<LockB, _>(&state);
131//! // compile error: LockB does not implement LockBefore<LockA>
132//! let a = locked_b.lock::<LockA, _>(&state);
133//! ```
134//!
135//! Even if the lock guard goes out of scope, the new `Locked` instance returned
136//! by [Locked::lock_and] will prevent the original one from being used to
137//! access state. This doesn't work:
138//!
139//! ```compile_fail
140//! # use fuchsia_sync::Mutex;
141//! # use starnix_sync::{lock_ordering, lock::LockFor, Locked, Unlocked};
142//! #
143//! # #[derive(Default)]
144//! # struct HoldsLocks {
145//! #     a: Mutex<u8>,
146//! #     b: Mutex<u32>,
147//! # }
148//! #
149//! # lock_ordering! {
150//! #    // LockA is the top of the lock hierarchy.
151//! #    Unlocked => LockA,
152//! #    // LockA can be acquired before LockB.
153//! #    LockA => LockB,
154//! # }
155//! #
156//! # impl LockFor<LockA> for HoldsLocks {
157//! #     type Data = u8;
158//! #     type Guard<'l> = fuchsia_sync::MutexGuard<'l, u8>
159//! #         where Self: 'l;
160//! #     fn lock(&self) -> Self::Guard<'_> {
161//! #         self.a.lock().unwrap()
162//! #     }
163//! # }
164//! #
165//! # impl LockFor<LockB> for HoldsLocks {
166//! #     type Data = u32;
167//! #     type Guard<'l> = fuchsia_sync::MutexGuard<'l, u32>
168//! #         where Self: 'l;
169//! #     fn lock(&self) -> Self::Guard<'_> {
170//! #         self.b.lock().unwrap()
171//! #     }
172//! # }
173//!
174//! let state = HoldsLocks::default();
175//! let locked = Unlocked::new();
176//!
177//! let (b, locked_b) = locked.lock_and::<LockB, _>();
178//! drop(b);
179//! let b = locked_b.lock::<LockB, _>(&state);
180//! // Won't work; `locked` is mutably borrowed by `locked_b`.
181//! let a = locked.lock::<LockA, _>(&state);
182//! ```
183
184use core::marker::PhantomData;
185use static_assertions::const_assert_eq;
186
187pub use crate::{LockBefore, LockEqualOrBefore, LockFor, RwLockFor};
188
189/// Enforcement mechanism for lock ordering.
190///
191/// `Locked` is a context that holds the lock level marker. Any state that
192/// requires a lock to access should acquire this lock by calling `lock_and`
193/// on a `Locked` object that is of an appropriate lock level. Acquiring
194/// a lock in this way produces the guard and a new `Locked` instance
195/// (with a different lock level) that mutably borrows from the original
196/// instance. This means the original instance can't be used to acquire
197/// new locks until the new instance leaves scope.
198pub struct Locked<L>(PhantomData<L>);
199
200impl<L> std::fmt::Debug for Locked<L> {
201    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> Result<(), std::fmt::Error> {
202        f.debug_struct(std::any::type_name::<Self>()).finish()
203    }
204}
205
206/// "Highest" lock level
207///
208/// The lock level for the thing returned by `Locked::new`. Users of this crate
209/// should implement `LockAfter<Unlocked>` for the root of any lock ordering
210/// trees.
211pub enum Unlocked {}
212
213const_assert_eq!(std::mem::size_of::<Locked<Unlocked>>(), 0);
214
215impl Unlocked {
216    /// Entry point for locked access.
217    ///
218    /// `Unlocked` is the "root" lock level and can be acquired before any lock.
219    ///
220    /// # Safety
221    /// `Unlocked` should only be used before any lock in the program has been acquired.
222    #[inline(always)]
223    pub unsafe fn new() -> &'static mut Locked<Unlocked> {
224        Locked::fabricate()
225    }
226
227    /// Entry point for locked access.
228    ///
229    /// `Unlocked` is the "root" lock level and can be acquired before any lock.
230    ///
231    /// # Safety
232    /// `Unlocked` should only be used before any lock in the program has been acquired.
233    #[inline(always)]
234    pub unsafe fn new_instance() -> Locked<Unlocked> {
235        Locked::<Unlocked>(Default::default())
236    }
237}
238impl LockEqualOrBefore<Unlocked> for Unlocked {}
239
240impl<L> Locked<L> {
241    /// Acquire the given lock.
242    ///
243    /// This requires that `M` can be locked after `L`.
244    #[inline(always)]
245    pub fn lock<'a, M, S>(&'a mut self, source: &'a S) -> S::Guard<'a>
246    where
247        M: 'a,
248        S: LockFor<M>,
249        L: LockBefore<M>,
250    {
251        let (data, _) = self.lock_and::<M, S>(source);
252        data
253    }
254
255    /// Acquire the given lock and a new locked context.
256    ///
257    /// This requires that `M` can be locked after `L`.
258    #[inline(always)]
259    pub fn lock_and<'a, M, S>(&'a mut self, source: &'a S) -> (S::Guard<'a>, &'a mut Locked<M>)
260    where
261        M: 'a,
262        S: LockFor<M>,
263        L: LockBefore<M>,
264    {
265        let data = S::lock(source);
266        (data, Locked::fabricate())
267    }
268
269    /// Acquire two locks that are on the same level, in a consistent order (sorted by memory address) and return both guards
270    /// as well as the new locked context.
271    ///
272    /// This requires that `M` can be locked after `L`.
273    #[inline(always)]
274    pub fn lock_both_and<'a, M, S>(
275        &'a mut self,
276        source1: &'a S,
277        source2: &'a S,
278    ) -> (S::Guard<'a>, S::Guard<'a>, &mut Locked<M>)
279    where
280        M: 'a,
281        S: LockFor<M>,
282        L: LockBefore<M>,
283    {
284        let ptr1: *const S = source1;
285        let ptr2: *const S = source2;
286        if ptr1 < ptr2 {
287            let data1 = S::lock(source1);
288            let _token = crate::allow_subclass();
289            let data2 = S::lock(source2);
290            (data1, data2, Locked::fabricate())
291        } else {
292            let data2 = S::lock(source2);
293            let _token = crate::allow_subclass();
294            let data1 = S::lock(source1);
295            (data1, data2, Locked::fabricate())
296        }
297    }
298    /// Acquire two locks that are on the same level, in a consistent order (sorted by memory address) and return both guards.
299    ///
300    /// This requires that `M` can be locked after `L`.
301    #[inline(always)]
302    pub fn lock_both<'a, M, S>(
303        &'a mut self,
304        source1: &'a S,
305        source2: &'a S,
306    ) -> (S::Guard<'a>, S::Guard<'a>)
307    where
308        M: 'a,
309        S: LockFor<M>,
310        L: LockBefore<M>,
311    {
312        let (data1, data2, _) = self.lock_both_and(source1, source2);
313        (data1, data2)
314    }
315
316    /// Attempt to acquire the given read lock and a new locked context.
317    ///
318    /// For accessing state via reader/writer locks. This requires that `M` can
319    /// be locked after `L`.
320    #[inline(always)]
321    pub fn read_lock_and<'a, M, S>(
322        &'a mut self,
323        source: &'a S,
324    ) -> (S::ReadGuard<'a>, &mut Locked<M>)
325    where
326        M: 'a,
327        S: RwLockFor<M>,
328        L: LockBefore<M>,
329    {
330        let data = S::read_lock(source);
331        (data, Locked::fabricate())
332    }
333
334    /// Attempt to acquire the given read lock.
335    ///
336    /// For accessing state via reader/writer locks. This requires that `M` can
337    /// be locked after `L`.
338    #[inline(always)]
339    pub fn read_lock<'a, M, S>(&'a mut self, source: &'a S) -> S::ReadGuard<'a>
340    where
341        M: 'a,
342        S: RwLockFor<M>,
343        L: LockBefore<M>,
344    {
345        let (data, _) = self.read_lock_and::<M, S>(source);
346        data
347    }
348
349    /// Attempt to acquire the given write lock and a new locked context.
350    ///
351    /// For accessing state via reader/writer locks. This requires that `M` can
352    /// be locked after `L`.
353    #[inline(always)]
354    pub fn write_lock_and<'a, M, S>(
355        &'a mut self,
356        source: &'a S,
357    ) -> (S::WriteGuard<'a>, &mut Locked<M>)
358    where
359        M: 'a,
360        S: RwLockFor<M>,
361        L: LockBefore<M>,
362    {
363        let data = S::write_lock(source);
364        (data, Locked::fabricate())
365    }
366
367    /// Attempt to acquire the given write lock.
368    ///
369    /// For accessing state via reader/writer locks. This requires that `M` can
370    /// be locked after `L`.
371    #[inline(always)]
372    pub fn write_lock<'a, M, S>(&'a mut self, source: &'a S) -> S::WriteGuard<'a>
373    where
374        M: 'a,
375        S: RwLockFor<M>,
376        L: LockBefore<M>,
377    {
378        let (data, _) = self.write_lock_and::<M, S>(source);
379        data
380    }
381
382    /// Restrict locking as if a lock was acquired.
383    ///
384    /// Like `lock_and` but doesn't actually acquire the lock `M`. This is
385    /// safe because any locks that could be acquired with the lock `M` held can
386    /// also be acquired without `M` being held.
387    #[inline(always)]
388    pub fn cast_locked<M>(&mut self) -> &mut Locked<M>
389    where
390        L: LockEqualOrBefore<M>,
391    {
392        Locked::fabricate()
393    }
394
395    const CHECK_ZST: () = assert!(std::mem::size_of::<Self>() == 0, "Locked<T> must be a ZST");
396    fn fabricate<'a>() -> &'a mut Self {
397        let _ = Self::CHECK_ZST;
398        // SAFETY: As confirmed by the preceding assert, `Self`
399        // is a ZST. `NonNull::as_mut` requires that the pointer is convertible
400        // to a reference [1], which in turn requires the following [2]:
401        // - The pointer is properly aligned (guaranteed by `NonNull::dangling`)
402        // - Non-null (guaranteed by invariant on `NonNull`)
403        // - Dereferenceable (guaranteed for all zero-sized pointers [3])
404        // - Points to a valid referent (trivially true for any zero-sized referent)
405        // - Satisfies Rust's aliasing rules (trivially true for any zero-sized referent)
406        //
407        // [1] https://doc.rust-lang.org/1.87.0/std/ptr/struct.NonNull.html#method.as_mut
408        // [2] https://doc.rust-lang.org/1.87.0/std/ptr/index.html#pointer-to-reference-conversion
409        // [3] https://doc.rust-lang.org/1.87.0/std/ptr/index.html#safety
410        unsafe { std::ptr::NonNull::dangling().as_mut() }
411    }
412}
413
414#[cfg(test)]
415mod test {
416    use fuchsia_sync::{Mutex, MutexGuard, RwLock, RwLockReadGuard, RwLockWriteGuard};
417
418    #[test]
419    fn example() {
420        use crate::{Unlocked, lock_ordering};
421
422        #[derive(Default)]
423        pub struct HoldsLocks {
424            a: Mutex<u8>,
425            b: Mutex<u32>,
426        }
427
428        lock_ordering! {
429            // LockA is the top of the lock hierarchy.
430            Unlocked => LockA,
431            // LockA can be acquired before LockB.
432            LockA => LockB,
433        }
434
435        impl LockFor<LockA> for HoldsLocks {
436            type Data = u8;
437            type Guard<'l>
438                = fuchsia_sync::MutexGuard<'l, u8>
439            where
440                Self: 'l;
441            fn lock(&self) -> Self::Guard<'_> {
442                self.a.lock()
443            }
444        }
445
446        impl LockFor<LockB> for HoldsLocks {
447            type Data = u32;
448            type Guard<'l>
449                = fuchsia_sync::MutexGuard<'l, u32>
450            where
451                Self: 'l;
452            fn lock(&self) -> Self::Guard<'_> {
453                self.b.lock()
454            }
455        }
456
457        // Accessing locked state looks like this:
458
459        let state = HoldsLocks::default();
460        // Create a new lock session with the "root" lock level (empty tuple).
461        #[allow(
462            clippy::undocumented_unsafe_blocks,
463            reason = "Force documented unsafe blocks in Starnix"
464        )]
465        let locked = unsafe { Unlocked::new() };
466        // Access locked state.
467        let (_a, locked_a) = locked.lock_and::<LockA, _>(&state);
468        let _b = locked_a.lock::<LockB, _>(&state);
469    }
470
471    mod lock_levels {
472        use crate::Unlocked;
473        use lock_ordering_macro::lock_ordering;
474        // Lock ordering tree:
475        // A -> B -> {C, D, E -> F, G -> H}
476        lock_ordering! {
477            Unlocked => A,
478            A => B,
479            B => C,
480            B => D,
481            B => E,
482            E => F,
483            B => G,
484            G => H,
485        }
486    }
487
488    use crate::{LockFor, RwLockFor, Unlocked};
489    use lock_levels::{A, B, C, D, E, F, G, H};
490
491    /// Data type with multiple locked fields.
492    #[derive(Default)]
493    pub struct Data {
494        a: Mutex<u8>,
495        b: Mutex<u16>,
496        c: Mutex<u64>,
497        d: RwLock<u128>,
498        e: Mutex<Mutex<u8>>,
499        g: Mutex<Vec<Mutex<u8>>>,
500        u: usize,
501    }
502
503    impl LockFor<A> for Data {
504        type Data = u8;
505        type Guard<'l> = MutexGuard<'l, u8>;
506        fn lock(&self) -> Self::Guard<'_> {
507            self.a.lock()
508        }
509    }
510
511    impl LockFor<B> for Data {
512        type Data = u16;
513        type Guard<'l> = MutexGuard<'l, u16>;
514        fn lock(&self) -> Self::Guard<'_> {
515            self.b.lock()
516        }
517    }
518
519    impl LockFor<C> for Data {
520        type Data = u64;
521        type Guard<'l> = MutexGuard<'l, u64>;
522        fn lock(&self) -> Self::Guard<'_> {
523            self.c.lock()
524        }
525    }
526
527    impl RwLockFor<D> for Data {
528        type Data = u128;
529        type ReadGuard<'l> = RwLockReadGuard<'l, u128>;
530        type WriteGuard<'l> = RwLockWriteGuard<'l, u128>;
531        fn read_lock(&self) -> Self::ReadGuard<'_> {
532            self.d.read()
533        }
534        fn write_lock(&self) -> Self::WriteGuard<'_> {
535            self.d.write()
536        }
537    }
538
539    impl LockFor<E> for Data {
540        type Data = Mutex<u8>;
541        type Guard<'l> = MutexGuard<'l, Mutex<u8>>;
542        fn lock(&self) -> Self::Guard<'_> {
543            self.e.lock()
544        }
545    }
546
547    impl LockFor<F> for Mutex<u8> {
548        type Data = u8;
549        type Guard<'l> = MutexGuard<'l, u8>;
550        fn lock(&self) -> Self::Guard<'_> {
551            self.lock()
552        }
553    }
554
555    impl LockFor<G> for Data {
556        type Data = Vec<Mutex<u8>>;
557        type Guard<'l> = MutexGuard<'l, Vec<Mutex<u8>>>;
558        fn lock(&self) -> Self::Guard<'_> {
559            self.g.lock()
560        }
561    }
562
563    impl LockFor<H> for Mutex<u8> {
564        type Data = u8;
565        type Guard<'l> = MutexGuard<'l, u8>;
566        fn lock(&self) -> Self::Guard<'_> {
567            self.lock()
568        }
569    }
570
571    #[derive(Debug)]
572    #[allow(dead_code)]
573    struct NotPresent;
574
575    #[test]
576    fn lock_a_then_c() {
577        let data = Data::default();
578
579        #[allow(
580            clippy::undocumented_unsafe_blocks,
581            reason = "Force documented unsafe blocks in Starnix"
582        )]
583        let w = unsafe { Unlocked::new() };
584        let (_a, wa) = w.lock_and::<A, _>(&data);
585        let (_c, _wc) = wa.lock_and::<C, _>(&data);
586        // This won't compile!
587        // let _b = _wc.lock::<B, _>(&data);
588    }
589
590    #[test]
591    fn cast_a_then_c() {
592        let data = Data::default();
593
594        #[allow(
595            clippy::undocumented_unsafe_blocks,
596            reason = "Force documented unsafe blocks in Starnix"
597        )]
598        let w = unsafe { Unlocked::new() };
599        let wa = w.cast_locked::<A>();
600        let (_c, _wc) = wa.lock_and::<C, _>(&data);
601        // This should not compile:
602        // let _b = w.lock::<B, _>(&data);
603    }
604
605    #[test]
606    fn unlocked_access_does_not_prevent_locking() {
607        let data = Data { a: Mutex::new(15), u: 34, ..Data::default() };
608
609        #[allow(
610            clippy::undocumented_unsafe_blocks,
611            reason = "Force documented unsafe blocks in Starnix"
612        )]
613        let locked = unsafe { Unlocked::new() };
614        let u = &data.u;
615
616        // Prove that `u` does not prevent locked state from being accessed.
617        let a = locked.lock::<A, _>(&data);
618
619        assert_eq!(u, &34);
620        assert_eq!(&*a, &15);
621    }
622
623    #[test]
624    fn nested_locks() {
625        let data = Data { e: Mutex::new(Mutex::new(1)), ..Data::default() };
626
627        #[allow(
628            clippy::undocumented_unsafe_blocks,
629            reason = "Force documented unsafe blocks in Starnix"
630        )]
631        let locked = unsafe { Unlocked::new() };
632        let (e, next_locked) = locked.lock_and::<E, _>(&data);
633        let v = next_locked.lock::<F, _>(&*e);
634        assert_eq!(*v, 1);
635    }
636
637    #[test]
638    fn rw_lock() {
639        let data = Data { d: RwLock::new(1), ..Data::default() };
640
641        #[allow(
642            clippy::undocumented_unsafe_blocks,
643            reason = "Force documented unsafe blocks in Starnix"
644        )]
645        let locked = unsafe { Unlocked::new() };
646        {
647            let mut d = locked.write_lock::<D, _>(&data);
648            *d = 10;
649        }
650        let d = locked.read_lock::<D, _>(&data);
651        assert_eq!(*d, 10);
652    }
653
654    #[test]
655    fn collections() {
656        let data = Data { g: Mutex::new(vec![Mutex::new(0), Mutex::new(1)]), ..Data::default() };
657
658        #[allow(
659            clippy::undocumented_unsafe_blocks,
660            reason = "Force documented unsafe blocks in Starnix"
661        )]
662        let locked = unsafe { Unlocked::new() };
663        let (g, next_locked) = locked.lock_and::<G, _>(&data);
664        let v = next_locked.lock::<H, _>(&g[1]);
665        assert_eq!(*v, 1);
666    }
667
668    #[test]
669    fn lock_same_level() {
670        let data1 = Data { a: Mutex::new(5), b: Mutex::new(15), ..Data::default() };
671        let data2 = Data { a: Mutex::new(10), b: Mutex::new(20), ..Data::default() };
672        #[allow(
673            clippy::undocumented_unsafe_blocks,
674            reason = "Force documented unsafe blocks in Starnix"
675        )]
676        let locked = unsafe { Unlocked::new() };
677        {
678            let (a1, a2, new_locked) = locked.lock_both_and::<A, _>(&data1, &data2);
679            assert_eq!(*a1, 5);
680            assert_eq!(*a2, 10);
681            let (b1, b2) = new_locked.lock_both::<B, _>(&data1, &data2);
682            assert_eq!(*b1, 15);
683            assert_eq!(*b2, 20);
684        }
685        {
686            let (a2, a1) = locked.lock_both::<A, _>(&data2, &data1);
687            assert_eq!(*a1, 5);
688            assert_eq!(*a2, 10);
689        }
690    }
691}