Skip to main content

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}