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}