osal_rs/async_primitives/mutex.rs
1/***************************************************************************
2 *
3 * osal-rs
4 * Copyright (C) 2026 Antonio Salsi <passy.linux@zresa.it>
5 *
6 * This library is free software; you can redistribute it and/or
7 * modify it under the terms of the GNU Lesser General Public
8 * License as published by the Free Software Foundation; either
9 * version 2.1 of the License, or (at your option) any later version.
10 *
11 * This library is distributed in the hope that it will be useful,
12 * but WITHOUT ANY WARRANTY; without even the implied warranty of
13 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
14 * Lesser General Public License for more details.
15 *
16 * You should have received a copy of the GNU Lesser General Public
17 * License along with this library; if not, see <https://www.gnu.org/licenses/>.
18 *
19 ***************************************************************************/
20
21//! Async mutual-exclusion lock built on an OSAL binary semaphore.
22//!
23//! # Design
24//!
25//! [`AsyncMutex<T>`] uses a binary semaphore (max=1, initial=1 — starts
26//! unlocked) instead of `os::Mutex`. This avoids a circular dependency:
27//! an async mutex would need to protect a waker with a mutex, which would
28//! in turn need a waker, and so on. An OSAL semaphore breaks the cycle.
29//!
30//! [`AsyncMutexGuard`] releases the lock and wakes the next waiter when
31//! dropped, providing RAII semantics identical to `std::sync::MutexGuard`.
32
33use core::cell::UnsafeCell;
34use core::future::Future;
35use core::ops::{Deref, DerefMut};
36use core::pin::Pin;
37use core::task::{Context, Poll};
38use core::time::Duration;
39
40use alloc::sync::Arc;
41
42use crate::os::Semaphore;
43use crate::traits::SemaphoreFn;
44use crate::utils::OsalRsBool;
45
46use super::waker_slot::WakerSlot;
47
48/// An async mutex protecting a value of type `T`.
49///
50/// # Fairness
51///
52/// This is a simple non-fair mutex: woken tasks compete to re-acquire the
53/// lock, so starvation is theoretically possible under high contention.
54/// For most embedded use-cases this is acceptable.
55///
56/// # Panics
57///
58/// Panics if the OS fails to allocate the internal binary semaphore.
59pub struct AsyncMutex<T> {
60 sem: Arc<Semaphore>,
61 waker: Arc<WakerSlot>,
62 data: UnsafeCell<T>,
63}
64
65// SAFETY: T is Send, and we protect access through the semaphore.
66unsafe impl<T: Send> Send for AsyncMutex<T> {}
67unsafe impl<T: Send> Sync for AsyncMutex<T> {}
68
69impl<T> AsyncMutex<T> {
70 /// Creates a new `AsyncMutex` protecting `value`.
71 ///
72 /// # Panics
73 ///
74 /// Panics if the underlying OSAL semaphore cannot be created.
75 pub fn new(value: T) -> Self {
76 let sem = Semaphore::new(1, 1)
77 .expect("AsyncMutex: failed to create binary semaphore");
78 Self {
79 sem: Arc::new(sem),
80 waker: Arc::new(WakerSlot::new()),
81 data: UnsafeCell::new(value),
82 }
83 }
84
85 /// Returns a `Future` that resolves to an [`AsyncMutexGuard`].
86 pub fn lock(&self) -> LockFuture<'_, T> {
87 LockFuture { mutex: self }
88 }
89}
90
91/// RAII guard returned by [`AsyncMutex::lock`].
92///
93/// Releases the lock and wakes the next async waiter when dropped.
94pub struct AsyncMutexGuard<'a, T> {
95 mutex: &'a AsyncMutex<T>,
96}
97
98impl<'a, T> Deref for AsyncMutexGuard<'a, T> {
99 type Target = T;
100 fn deref(&self) -> &T {
101 // SAFETY: we hold the semaphore, so we have exclusive access.
102 unsafe { &*self.mutex.data.get() }
103 }
104}
105
106impl<'a, T> DerefMut for AsyncMutexGuard<'a, T> {
107 fn deref_mut(&mut self) -> &mut T {
108 // SAFETY: we hold the semaphore, so we have exclusive mutable access.
109 unsafe { &mut *self.mutex.data.get() }
110 }
111}
112
113impl<'a, T> Drop for AsyncMutexGuard<'a, T> {
114 fn drop(&mut self) {
115 self.mutex.sem.signal();
116 self.mutex.waker.wake();
117 }
118}
119
120/// Future returned by [`AsyncMutex::lock`].
121pub struct LockFuture<'a, T> {
122 mutex: &'a AsyncMutex<T>,
123}
124
125impl<'a, T> Future for LockFuture<'a, T> {
126 type Output = AsyncMutexGuard<'a, T>;
127
128 fn poll(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<Self::Output> {
129 // Non-blocking try: wait with zero timeout.
130 if self.mutex.sem.wait(Duration::ZERO) == OsalRsBool::True {
131 return Poll::Ready(AsyncMutexGuard { mutex: self.mutex });
132 }
133
134 // Store waker, then double-check to close the TOCTOU window.
135 self.mutex.waker.store(cx.waker());
136
137 if self.mutex.sem.wait(Duration::ZERO) == OsalRsBool::True {
138 Poll::Ready(AsyncMutexGuard { mutex: self.mutex })
139 } else {
140 Poll::Pending
141 }
142 }
143}