starnix_sync/
lock_relations.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//! Traits used to describe lock-ordering relationships in a way that does not
6//! introduce cycles.
7//!
8//! This module defines the traits used to describe lock-ordering relationships,
9//! [`LockBefore`] and [`LockAfter`]. They are reciprocals, like [`From`] and
10//! [`Into`] in the standard library, and like `From` and `Into`, a blanket impl
11//! is provided for `LockBefore` for implementations of `LockAfter`.
12//!
13//! It's recommended to use `lock_ordering!` macro to construct the lock ordering
14//! graph instead of implementing `LockAfter<B> for A` on your own lock level
15//! types `A` and `B`. This is because the macro implements `LockBefore` for all
16//! the levels that a given level is reachable from, but also because it checks
17//! the graph for the absence of cycles.
18
19/// Marker trait that indicates that `Self` can be locked after `A`.
20///
21/// This should be implemented for lock types to specify that, in the lock
22/// ordering graph, `A` comes before `Self`. So if `B: LockAfter<A>`, lock type
23/// `B` can be acquired after `A` but `A` cannot be acquired after `B`.
24///
25/// Note, though, that it's preferred to use the [`lock_ordering`] macro
26/// instead of writing trait impls directly to avoid the possibility of lock
27/// ordering cycles.
28pub trait LockAfter<A> {}
29
30/// Marker trait that indicates that `Self` is an ancestor of `X`.
31///
32/// Functions and trait impls that want to apply lock ordering bounds should use
33/// this instead of [`LockAfter`]. Types should prefer to implement `LockAfter`
34/// instead of this trait. Like [`From`] and [`Into`], a blanket impl of
35/// `LockBefore` is provided for all types that implement `LockAfter`
36pub trait LockBefore<X> {}
37
38impl<B: LockAfter<A>, A> LockBefore<B> for A {}
39
40/// Marker trait that indicates that `Self` is `X` or an ancestor of `X`.
41///
42/// Functions and trait impls that want to apply lock ordering bounds should
43/// prefer [`LockBefore`]. However, there are some situations where using
44/// template to apply lock ordering bounds is impossible, so a fixed level
45/// must be used instead. In that case, `LockEqualOrBefore` can be used as
46/// a workaround to avoid restricting other methods to just the fixed level.
47/// See the tests for the example.
48/// Note: Any type representing a lock level must explicitly implement
49/// `LockEqualOrBefore<X> for X` (or use a `lock_ordering` macro) for this to
50/// work.
51pub trait LockEqualOrBefore<X> {}
52
53impl<B, A> LockEqualOrBefore<B> for A where A: LockBefore<B> {}
54
55// Define a lock level that corresponds to some state that can be locked.
56#[macro_export]
57macro_rules! lock_level {
58    ($A:ident) => {
59        pub enum $A {}
60        impl $crate::LockEqualOrBefore<$A> for $A {}
61        static_assertions::const_assert_eq!(std::mem::size_of::<$crate::Locked<'static, $A>>(), 0);
62    };
63}
64
65#[cfg(test)]
66mod test {
67    use crate::{LockBefore, LockEqualOrBefore, LockFor, Locked, Unlocked};
68    use lock_ordering_macro::lock_ordering;
69    use std::sync::{Mutex, MutexGuard};
70    extern crate self as starnix_sync;
71
72    lock_ordering! {
73        Unlocked => A,
74        A => B,
75        B => C,
76    }
77
78    pub struct FakeLocked {
79        a: Mutex<u32>,
80        c: Mutex<char>,
81    }
82
83    impl LockFor<A> for FakeLocked {
84        type Data = u32;
85        type Guard<'l>
86            = MutexGuard<'l, u32>
87        where
88            Self: 'l;
89        fn lock(&self) -> Self::Guard<'_> {
90            self.a.lock().unwrap()
91        }
92    }
93
94    impl LockFor<C> for FakeLocked {
95        type Data = char;
96        type Guard<'l>
97            = MutexGuard<'l, char>
98        where
99            Self: 'l;
100        fn lock(&self) -> Self::Guard<'_> {
101            self.c.lock().unwrap()
102        }
103    }
104
105    #[test]
106    fn lock_a_then_c() {
107        const A_DATA: u32 = 123;
108        const C_DATA: char = '4';
109        let state = FakeLocked { a: A_DATA.into(), c: C_DATA.into() };
110
111        let mut locked = unsafe { Unlocked::new() };
112
113        let (a, mut locked) = locked.lock_and::<A, _>(&state);
114        assert_eq!(*a, A_DATA);
115        // Show that A: LockBefore<B> and B: LockBefore<C> => A: LockBefore<C>.
116        // Otherwise this wouldn't compile:
117        let c = locked.lock::<C, _>(&state);
118        assert_eq!(*c, C_DATA);
119    }
120
121    fn access_c<L: LockBefore<C>>(locked: &mut Locked<'_, L>, state: &FakeLocked) -> char {
122        let c = locked.lock::<C, _>(state);
123        *c
124    }
125
126    // Uses a fixed level B
127    fn fixed1(locked: &mut Locked<'_, B>, state: &FakeLocked) -> char {
128        relaxed(locked, state)
129    }
130
131    // Uses a fixed level B
132    fn fixed2(locked: &mut Locked<'_, B>, state: &FakeLocked) -> char {
133        access_c(locked, state)
134    }
135
136    // `LockBefore<B>` cannot be used here because then it would be impossible
137    // to use it from fixed1. `LockBefore<C>` can't be used because then
138    // `cast_locked::<B>` wouldn't work (there could be other ancestors of `C`)
139    fn relaxed<L>(locked: &mut Locked<'_, L>, state: &FakeLocked) -> char
140    where
141        L: LockEqualOrBefore<B>,
142    {
143        let mut locked = locked.cast_locked::<B>();
144        fixed2(&mut locked, state)
145    }
146
147    #[test]
148    fn lock_equal_or_before() {
149        const A_DATA: u32 = 123;
150        const C_DATA: char = '4';
151        let state = FakeLocked { a: A_DATA.into(), c: C_DATA.into() };
152        let mut locked = unsafe { Unlocked::new() };
153
154        assert_eq!(relaxed(&mut locked, &state), C_DATA);
155        let mut locked = locked.cast_locked::<A>();
156        assert_eq!(relaxed(&mut locked, &state), C_DATA);
157        let mut locked = locked.cast_locked::<B>();
158        assert_eq!(relaxed(&mut locked, &state), C_DATA);
159        assert_eq!(fixed1(&mut locked, &state), C_DATA);
160        // This won't compile as C: LockEqualOrBefore<B> is not satisfied.
161        // let mut locked = locked.cast_locked::<C>();
162        // assert_eq!(relaxed(&mut locked, &state), C_DATA);
163    }
164
165    #[test]
166    fn build_graph_test() {
167        lock_ordering! {
168            Unlocked => LevelA,
169            Unlocked => LevelB,
170            LevelB => LevelC,
171            LevelA => LevelC
172            LevelC => LevelD
173            // This creates a cycle:
174            // LevelD => LevelA,
175        }
176
177        #[derive(Default)]
178        pub struct HoldsLocks {
179            a: Mutex<u8>,
180        }
181
182        impl LockFor<LevelA> for HoldsLocks {
183            type Data = u8;
184            type Guard<'l>
185                = std::sync::MutexGuard<'l, u8>
186            where
187                Self: 'l;
188            fn lock(&self) -> Self::Guard<'_> {
189                self.a.lock().unwrap()
190            }
191        }
192
193        let state = HoldsLocks::default();
194        // Create a new lock session with the "root" lock level.
195        let mut locked = unsafe { Unlocked::new() };
196        // Access locked state.
197        let (_a, _locked_a) = locked.lock_and::<LevelA, _>(&state);
198    }
199}