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<$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        #[allow(
112            clippy::undocumented_unsafe_blocks,
113            reason = "Force documented unsafe blocks in Starnix"
114        )]
115        let locked = unsafe { Unlocked::new() };
116
117        let (a, locked) = locked.lock_and::<A, _>(&state);
118        assert_eq!(*a, A_DATA);
119        // Show that A: LockBefore<B> and B: LockBefore<C> => A: LockBefore<C>.
120        // Otherwise this wouldn't compile:
121        let c = locked.lock::<C, _>(&state);
122        assert_eq!(*c, C_DATA);
123    }
124
125    fn access_c<L: LockBefore<C>>(locked: &mut Locked<L>, state: &FakeLocked) -> char {
126        let c = locked.lock::<C, _>(state);
127        *c
128    }
129
130    // Uses a fixed level B
131    fn fixed1(locked: &mut Locked<B>, state: &FakeLocked) -> char {
132        relaxed(locked, state)
133    }
134
135    // Uses a fixed level B
136    fn fixed2(locked: &mut Locked<B>, state: &FakeLocked) -> char {
137        access_c(locked, state)
138    }
139
140    // `LockBefore<B>` cannot be used here because then it would be impossible
141    // to use it from fixed1. `LockBefore<C>` can't be used because then
142    // `cast_locked::<B>` wouldn't work (there could be other ancestors of `C`)
143    fn relaxed<L>(locked: &mut Locked<L>, state: &FakeLocked) -> char
144    where
145        L: LockEqualOrBefore<B>,
146    {
147        let locked = locked.cast_locked::<B>();
148        fixed2(locked, state)
149    }
150
151    #[test]
152    fn lock_equal_or_before() {
153        const A_DATA: u32 = 123;
154        const C_DATA: char = '4';
155        let state = FakeLocked { a: A_DATA.into(), c: C_DATA.into() };
156        #[allow(
157            clippy::undocumented_unsafe_blocks,
158            reason = "Force documented unsafe blocks in Starnix"
159        )]
160        let locked = unsafe { Unlocked::new() };
161
162        assert_eq!(relaxed(locked, &state), C_DATA);
163        let locked = locked.cast_locked::<A>();
164        assert_eq!(relaxed(locked, &state), C_DATA);
165        let locked = locked.cast_locked::<B>();
166        assert_eq!(relaxed(locked, &state), C_DATA);
167        assert_eq!(fixed1(locked, &state), C_DATA);
168        // This won't compile as C: LockEqualOrBefore<B> is not satisfied.
169        // let locked = locked.cast_locked::<C>();
170        // assert_eq!(relaxed(locked, &state), C_DATA);
171    }
172
173    #[test]
174    fn build_graph_test() {
175        lock_ordering! {
176            Unlocked => LevelA,
177            Unlocked => LevelB,
178            LevelB => LevelC,
179            LevelA => LevelC
180            LevelC => LevelD
181            // This creates a cycle:
182            // LevelD => LevelA,
183        }
184
185        #[derive(Default)]
186        pub struct HoldsLocks {
187            a: Mutex<u8>,
188        }
189
190        impl LockFor<LevelA> for HoldsLocks {
191            type Data = u8;
192            type Guard<'l>
193                = std::sync::MutexGuard<'l, u8>
194            where
195                Self: 'l;
196            fn lock(&self) -> Self::Guard<'_> {
197                self.a.lock().unwrap()
198            }
199        }
200
201        let state = HoldsLocks::default();
202        // Create a new lock session with the "root" lock level.
203        #[allow(
204            clippy::undocumented_unsafe_blocks,
205            reason = "Force documented unsafe blocks in Starnix"
206        )]
207        let locked = unsafe { Unlocked::new() };
208        // Access locked state.
209        let (_a, _locked_a) = locked.lock_and::<LevelA, _>(&state);
210    }
211}