obkrnl/lock/gutex/
mod.rs

1pub use self::guard::*;
2
3use super::MTX_UNOWNED;
4use crate::context::{BorrowedArc, current_thread};
5use alloc::rc::Rc;
6use alloc::sync::Arc;
7use core::cell::UnsafeCell;
8use core::marker::PhantomData;
9use core::sync::atomic::{AtomicUsize, Ordering};
10
11mod guard;
12
13/// A mutex that grant exclusive access to a group of members.
14///
15/// The [`crate::lock::Mutex`] is prone to deadlock when using on a multiple struct fields like
16/// this:
17///
18/// ```
19/// use crate::lock::Mutex;
20///
21/// pub struct Foo {
22///     field1: Mutex<()>,
23///     field2: Mutex<()>,
24/// }
25/// ```
26///
27/// The order to acquire the lock must be the same everywhere otherwise the deadlock is possible.
28/// Maintaining the lock order manually are cumbersome task so we introduce this type to handle this
29/// instead.
30///
31/// How this type are working is simple. Any locks on any member will lock the same mutex in the
32/// group, which mean there are only one mutex in the group. It have the same effect as the
33/// following code:
34///
35/// ```
36/// use crate::lock::Mutex;
37///
38/// pub struct Foo {
39///     data: Mutex<Data>,
40/// }
41///
42/// struct Data {
43///     field1: (),
44///     field2: (),
45/// }
46/// ```
47///
48/// The bonus point of this type is it will allow recursive lock for read-only access so you will
49/// never end up deadlock yourself. It will panic if you try to acquire write access while the
50/// readers are still active the same as [`core::cell::RefCell`].
51pub struct Gutex<T> {
52    group: Arc<GutexGroup>,
53    active: UnsafeCell<usize>,
54    value: UnsafeCell<T>,
55}
56
57impl<T> Gutex<T> {
58    /// Locks this [`Gutex`] with read-only access.
59    ///
60    /// Multiple read-only accesses can be taken out at the same time.
61    ///
62    /// # Panics
63    /// If there are an active write access to this [`Gutex`].
64    pub fn read(&self) -> GutexRead<T> {
65        // Check if there are an active writer.
66        let lock = self.group.lock();
67        let active = self.active.get();
68
69        // SAFETY: This is safe because we own the lock that protect both active and value.
70        unsafe {
71            if *active == usize::MAX {
72                panic!("attempt to acquire the read lock while there are an active write lock");
73            } else if *active == (usize::MAX - 1) {
74                // This should never happen because stack overflow should be triggering first.
75                panic!("maximum number of active readers has been reached");
76            }
77
78            *active += 1;
79
80            GutexRead::new(lock, active, self.value.get())
81        }
82    }
83
84    /// # Panics
85    /// If there are any active reader or writer.
86    pub fn write(&self) -> GutexWrite<T> {
87        // Check if there are active reader or writer.
88        let lock = self.group.lock();
89        let active = self.active.get();
90
91        // SAFETY: This is safe because we own the lock that protect both active and value.
92        unsafe {
93            if *active != 0 {
94                panic!(
95                    "attempt to acquire the write lock while there are an active reader or writer"
96                );
97            }
98
99            *active = usize::MAX;
100
101            GutexWrite::new(lock, active, self.value.get())
102        }
103    }
104}
105
106unsafe impl<T: Send> Send for Gutex<T> {}
107unsafe impl<T: Send> Sync for Gutex<T> {}
108
109/// Group of [`Gutex`].
110pub struct GutexGroup {
111    owning: AtomicUsize,
112    active: UnsafeCell<usize>,
113}
114
115impl GutexGroup {
116    /// # Context safety
117    /// This function does not require a CPU context on **stage 1** heap.
118    pub fn new() -> Arc<Self> {
119        Arc::new(Self {
120            owning: AtomicUsize::new(MTX_UNOWNED),
121            active: UnsafeCell::new(0),
122        })
123    }
124
125    /// # Context safety
126    /// This function does not require a CPU context if [`Default`] implementation on `T` does not.
127    pub fn spawn_default<T: Default>(self: Arc<Self>) -> Gutex<T> {
128        self.spawn(T::default())
129    }
130
131    /// # Context safety
132    /// This function does not require a CPU context.
133    pub fn spawn<T>(self: Arc<Self>, value: T) -> Gutex<T> {
134        Gutex {
135            group: self,
136            active: UnsafeCell::new(0),
137            value: UnsafeCell::new(value),
138        }
139    }
140
141    fn lock(&self) -> GroupGuard {
142        // Acquire the lock.
143        let td = current_thread();
144        let id = BorrowedArc::as_ptr(&td) as usize;
145
146        loop {
147            let owning = match self.owning.compare_exchange(
148                MTX_UNOWNED,
149                id,
150                Ordering::Acquire,
151                Ordering::Relaxed,
152            ) {
153                Ok(_) => break,
154                Err(v) => v,
155            };
156
157            if owning == id {
158                break;
159            }
160
161            self.wait();
162        }
163
164        // SAFETY: This is safe because the current thread acquire the lock successfully by the
165        // above compare_exchange().
166        unsafe { GroupGuard::new(self) }
167    }
168
169    #[inline(never)]
170    fn wait(&self) {
171        todo!()
172    }
173}
174
175unsafe impl Send for GutexGroup {}
176unsafe impl Sync for GutexGroup {}
177
178/// An RAII object used to release the lock on [`GutexGroup`]. This type cannot be send because it
179/// will cause data race on the group when dropping if more than one [`GroupGuard`] are active.
180struct GroupGuard<'a> {
181    group: &'a GutexGroup,
182    phantom: PhantomData<Rc<()>>, // For !Send and !Sync.
183}
184
185impl<'a> GroupGuard<'a> {
186    /// # Safety
187    /// The group must be locked by the calling thread with no active references to any of its
188    /// field.
189    unsafe fn new(group: &'a GutexGroup) -> Self {
190        unsafe { *group.active.get() += 1 };
191
192        Self {
193            group,
194            phantom: PhantomData,
195        }
196    }
197
198    #[inline(never)]
199    fn release(&mut self) {
200        self.group.owning.store(MTX_UNOWNED, Ordering::Release);
201
202        todo!("wakeup waiting thread");
203    }
204}
205
206impl Drop for GroupGuard<'_> {
207    fn drop(&mut self) {
208        unsafe {
209            let active = self.group.active.get();
210
211            *active -= 1;
212
213            if *active != 0 {
214                return;
215            }
216        }
217
218        self.release();
219    }
220}