Skip to content

Commit 102478e

Browse files
committed
zephyr: sync: Create Mutex/Condvar
Create higher-level Mutex and Condvar types that are similar to std::sync::Mutex and std::sync::Condvar. The main difference is that the only current constructor for this requires the sys Mutex and sys Condvar from the sys versions that are statically allocated. Signed-off-by: David Brown <[email protected]>
1 parent 2731deb commit 102478e

File tree

1 file changed

+237
-0
lines changed

1 file changed

+237
-0
lines changed

zephyr/src/sync.rs

Lines changed: 237 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,16 @@
55
//! [`crossbeam-channel`](https://docs.rs/crossbeam-channel/latest/crossbeam_channel/), in as much
66
//! as it makes sense.
77
8+
use core::{
9+
cell::UnsafeCell,
10+
fmt,
11+
marker::PhantomData,
12+
ops::{Deref, DerefMut},
13+
};
14+
15+
use crate::time::{Forever, NoWait};
16+
use crate::sys::sync as sys;
17+
818
pub mod atomic {
919
//! Re-export portable atomic.
1020
//!
@@ -22,3 +32,230 @@ pub mod atomic {
2232

2333
#[cfg(CONFIG_RUST_ALLOC)]
2434
pub use portable_atomic_util::Arc;
35+
36+
// Channels are currently only available with allocation. Bounded channels later might be
37+
// available.
38+
39+
/// Until poisoning is implemented, mutexes never return an error, and we just get back the guard.
40+
pub type LockResult<Guard> = Result<Guard, ()>;
41+
42+
/// The return type from [`Mutex::try_lock`].
43+
///
44+
/// The error indicates the reason for the failure. Until poisoning is
45+
/// implemented, there is only a single type of failure.
46+
pub type TryLockResult<Guard> = Result<Guard, TryLockError>;
47+
48+
/// An enumeration of possible errors associated with a [`TryLockResult`].
49+
///
50+
/// Note that until Poisoning is implemented, there is only one value of this.
51+
pub enum TryLockError {
52+
/// The lock could not be acquired at this time because the operation would otherwise block.
53+
WouldBlock,
54+
}
55+
56+
/// A mutual exclusion primitive useful for protecting shared data.
57+
///
58+
/// This mutex will block threads waiting for the lock to become available. This is modeled after
59+
/// [`std::sync::Mutex`](https://doc.rust-lang.org/stable/std/sync/struct.Mutex.html), and attempts
60+
/// to implement that API as closely as makes sense on Zephyr. Currently, it has the following
61+
/// differences:
62+
/// - Poisoning: This does not yet implement poisoning, as there is no way to recover from panic at
63+
/// this time on Zephyr.
64+
/// - Allocation: `new` is not yet provided, and will be provided once kernel object pools are
65+
/// implemented. Please use `new_from` which takes a reference to a statically allocated
66+
/// `sys::Mutex`.
67+
pub struct Mutex<T: ?Sized> {
68+
inner: sys::Mutex,
69+
// poison: ...
70+
data: UnsafeCell<T>,
71+
}
72+
73+
// At least if correctly done, the Mutex provides for Send and Sync as long as the inner data
74+
// supports Send.
75+
unsafe impl<T: ?Sized + Send> Send for Mutex<T> {}
76+
unsafe impl<T: ?Sized + Send> Sync for Mutex<T> {}
77+
78+
impl<T> fmt::Debug for Mutex<T> {
79+
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
80+
write!(f, "Mutex {:?}", self.inner)
81+
}
82+
}
83+
84+
/// An RAII implementation of a "scoped lock" of a mutex. When this structure is dropped (faslls
85+
/// out of scope), the lock will be unlocked.
86+
///
87+
/// The data protected by the mutex can be accessed through this guard via its [`Deref`] and
88+
/// [`DerefMut`] implementations.
89+
///
90+
/// This structure is created by the [`lock`] and [`try_lock`] methods on [`Mutex`].
91+
///
92+
/// [`lock`]: Mutex::lock
93+
/// [`try_lock`]: Mutex::try_lock
94+
///
95+
/// Taken directly from
96+
/// [`std::sync::MutexGuard`](https://doc.rust-lang.org/stable/std/sync/struct.MutexGuard.html).
97+
pub struct MutexGuard<'a, T: ?Sized + 'a> {
98+
lock: &'a Mutex<T>,
99+
// until <https://github.com/rust-lang/rust/issues/68318> is implemented, we have to mark unsend
100+
// explicitly. This can be done by holding Phantom data with an unsafe cell in it.
101+
_nosend: PhantomData<UnsafeCell<()>>,
102+
}
103+
104+
// Make sure the guard doesn't get sent.
105+
// Negative trait bounds are unstable, see marker above.
106+
// impl<T: ?Sized> !Send for MutexGuard<'_, T> {}
107+
unsafe impl<T: ?Sized + Sync> Sync for MutexGuard<'_, T> {}
108+
109+
impl<T> Mutex<T> {
110+
/// Construct a new wrapped Mutex, using the given underlying sys mutex. This is different that
111+
/// `std::sync::Mutex` in that in Zephyr, objects are frequently allocated statically, and the
112+
/// sys Mutex will be taken by this structure. It is safe to share the underlying Mutex between
113+
/// different items, but without careful use, it is easy to deadlock, so it is not recommended.
114+
pub const fn new_from(t: T, raw_mutex: sys::Mutex) -> Mutex<T> {
115+
Mutex { inner: raw_mutex, data: UnsafeCell::new(t) }
116+
}
117+
}
118+
119+
impl<T: ?Sized> Mutex<T> {
120+
/// Acquires a mutex, blocking the current thread until it is able to do so.
121+
///
122+
/// This function will block the local thread until it is available to acquire the mutex. Upon
123+
/// returning, the thread is the only thread with the lock held. An RAII guard is returned to
124+
/// allow scoped unlock of the lock. When the guard goes out of scope, the mutex will be
125+
/// unlocked.
126+
///
127+
/// In `std`, an attempt to lock a mutex by a thread that already holds the mutex is
128+
/// unspecified. Zephyr explicitly supports this behavior, by simply incrementing a lock
129+
/// count.
130+
pub fn lock(&self) -> LockResult<MutexGuard<'_, T>> {
131+
// With `Forever`, should never return an error.
132+
self.inner.lock(Forever).unwrap();
133+
unsafe {
134+
Ok(MutexGuard::new(self))
135+
}
136+
}
137+
138+
/// Attempts to acquire this lock.
139+
///
140+
/// If the lock could not be acquired at this time, then [`Err`] is returned. Otherwise, an RAII
141+
/// guard is returned. The lock will be unlocked when the guard is dropped.
142+
///
143+
/// This function does not block.
144+
pub fn try_lock(&self) -> TryLockResult<MutexGuard<'_, T>> {
145+
match self.inner.lock(NoWait) {
146+
Ok(()) => {
147+
unsafe {
148+
Ok(MutexGuard::new(self))
149+
}
150+
}
151+
// TODO: It might be better to distinguish these errors, and only return the WouldBlock
152+
// if that is the corresponding error. But, the lock shouldn't fail in Zephyr.
153+
Err(_) => {
154+
Err(TryLockError::WouldBlock)
155+
}
156+
}
157+
}
158+
}
159+
160+
impl<'mutex, T: ?Sized> MutexGuard<'mutex, T> {
161+
unsafe fn new(lock: &'mutex Mutex<T>) -> MutexGuard<'mutex, T> {
162+
// poison todo
163+
MutexGuard { lock, _nosend: PhantomData }
164+
}
165+
}
166+
167+
impl<T: ?Sized> Deref for MutexGuard<'_, T> {
168+
type Target = T;
169+
170+
fn deref(&self) -> &T {
171+
unsafe {
172+
&*self.lock.data.get()
173+
}
174+
}
175+
}
176+
177+
impl<T: ?Sized> DerefMut for MutexGuard<'_, T> {
178+
fn deref_mut(&mut self) -> &mut T {
179+
unsafe { &mut *self.lock.data.get() }
180+
}
181+
}
182+
183+
impl<T: ?Sized> Drop for MutexGuard<'_, T> {
184+
#[inline]
185+
fn drop(&mut self) {
186+
unsafe {
187+
self.lock.inner.unlock().unwrap();
188+
}
189+
}
190+
}
191+
192+
/// Inspired by
193+
/// [`std::sync::Condvar`](https://doc.rust-lang.org/stable/std/sync/struct.Condvar.html),
194+
/// implemented directly using `z_condvar` in Zephyr.
195+
///
196+
/// Condition variables represent the ability to block a thread such that it consumes no CPU time
197+
/// while waiting for an even to occur. Condition variables are typically associated with a
198+
/// boolean predicate (a condition) and a mutex. The predicate is always verified inside of the
199+
/// mutex before determining that a thread must block.
200+
///
201+
/// Functions in this module will block the current **thread** of execution. Note that any attempt
202+
/// to use multiple mutexces on the same condition variable may result in a runtime panic.
203+
pub struct Condvar {
204+
inner: sys::Condvar,
205+
}
206+
207+
impl Condvar {
208+
/// Construct a new wrapped Condvar, using the given underlying `k_condvar`.
209+
///
210+
/// This is different from `std::sync::Condvar` in that in Zephyr, objects are frequently
211+
/// allocated statically, and the sys Condvar will be taken by this structure.
212+
pub const fn new_from(raw_condvar: sys::Condvar) -> Condvar {
213+
Condvar { inner: raw_condvar }
214+
}
215+
216+
/// Blocks the current thread until this conditional variable receives a notification.
217+
///
218+
/// This function will automatically unlock the mutex specified (represented by `guard`) and
219+
/// block the current thread. This means that any calls to `notify_one` or `notify_all` which
220+
/// happen logically after the mutex is unlocked are candidates to wake this thread up. When
221+
/// this function call returns, the lock specified will have been re-equired.
222+
///
223+
/// Note that this function is susceptable to spurious wakeups. Condition variables normally
224+
/// have a boolean predicate associated with them, and the predicate must always be checked
225+
/// each time this function returns to protect against spurious wakeups.
226+
pub fn wait<'a, T>(&self, guard: MutexGuard<'a, T>) -> LockResult<MutexGuard<'a, T>> {
227+
self.inner.wait(&guard.lock.inner);
228+
Ok(guard)
229+
}
230+
231+
// TODO: wait_while
232+
// TODO: wait_timeout_ms
233+
// TODO: wait_timeout
234+
// TODO: wait_timeout_while
235+
236+
/// Wakes up one blocked thread on this condvar.
237+
///
238+
/// If there is a blocked thread on this condition variable, then it will be woken up from its
239+
/// call to `wait` or `wait_timeout`. Calls to `notify_one` are not buffered in any way.
240+
///
241+
/// To wakeup all threads, see `notify_all`.
242+
pub fn notify_one(&self) {
243+
self.inner.notify_one();
244+
}
245+
246+
/// Wakes up all blocked threads on this condvar.
247+
///
248+
/// This methods will ensure that any current waiters on the condition variable are awoken.
249+
/// Calls to `notify_all()` are not buffered in any way.
250+
///
251+
/// To wake up only one thread, see `notify_one`.
252+
pub fn notify_all(&self) {
253+
self.inner.notify_all();
254+
}
255+
}
256+
257+
impl fmt::Debug for Condvar {
258+
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
259+
write!(f, "Condvar {:?}", self.inner)
260+
}
261+
}

0 commit comments

Comments
 (0)