Skip to main content

rs_matter/utils/sync/
mutex.rs

1/*
2 *
3 *    Copyright (c) 2024-2026 Project CHIP Authors
4 *
5 *    Licensed under the Apache License, Version 2.0 (the "License");
6 *    you may not use this file except in compliance with the License.
7 *    You may obtain a copy of the License at
8 *
9 *        http://www.apache.org/licenses/LICENSE-2.0
10 *
11 *    Unless required by applicable law or agreed to in writing, software
12 *    distributed under the License is distributed on an "AS IS" BASIS,
13 *    WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14 *    See the License for the specific language governing permissions and
15 *    limitations under the License.
16 */
17
18//! A variation of the `embassy-sync` async mutex that only locks the mutex if a certain
19//! condition on the content of the data holds true.
20//! Check `embassy_sync::Mutex` for the original unconditional implementation.
21
22use core::cell::UnsafeCell;
23use core::ops::{Deref, DerefMut};
24
25use embassy_sync::blocking_mutex::raw::RawMutex;
26
27use crate::utils::init::{init, Init, UnsafeCellInit};
28use crate::utils::sync::blocking::raw::MatterRawMutex;
29
30use super::signal::Signal;
31
32/// Error returned by [`Mutex::try_lock`]
33#[derive(PartialEq, Eq, Clone, Copy, Debug)]
34#[cfg_attr(feature = "defmt", derive(defmt::Format))]
35pub struct TryLockError;
36
37/// Async mutex with conditional locking based on the data inside the mutex.
38/// Check `embassy_sync::Mutex` for the original unconditional implementation.
39pub struct IfMutex<T, M = MatterRawMutex>
40where
41    T: ?Sized,
42    M: RawMutex,
43{
44    state: Signal<bool, M>,
45    inner: UnsafeCell<T>,
46}
47
48unsafe impl<T: ?Sized + Send, M: RawMutex + Send> Send for IfMutex<T, M> {}
49unsafe impl<T: ?Sized + Send, M: RawMutex + Sync> Sync for IfMutex<T, M> {}
50
51/// Async mutex.
52impl<T, M> IfMutex<T, M>
53where
54    M: RawMutex,
55{
56    /// Create a new mutex with the given value.
57    #[inline(always)]
58    pub const fn new(value: T) -> Self {
59        Self {
60            state: Signal::new(false),
61            inner: UnsafeCell::new(value),
62        }
63    }
64
65    /// Creates a mutex in-place initializer with the given value initializer.
66    pub fn init<I: Init<T>>(value: I) -> impl Init<Self> {
67        init!(Self {
68            state: Signal::new(false),
69            inner <- UnsafeCell::init(value),
70        })
71    }
72}
73
74impl<T, M> IfMutex<T, M>
75where
76    T: ?Sized,
77    M: RawMutex,
78{
79    /// Lock the mutex.
80    ///
81    /// This will wait for the mutex to be unlocked if it's already locked.
82    pub async fn lock(&self) -> IfMutexGuard<'_, T, M> {
83        self.lock_if(|_| true).await
84    }
85
86    /// Lock the mutex.
87    ///
88    /// This will wait for the mutex to be unlocked if it's already locked _and_ for the provided condition on the data to become true.
89    pub async fn lock_if<F>(&self, f: F) -> IfMutexGuard<'_, T, M>
90    where
91        F: Fn(&T) -> bool,
92    {
93        self.state
94            .wait(|locked| {
95                // Safety: it is safe to access the unsafe cell data, because:
96                // - nobody holds the long term (async) lock on the mutex right now (`locked == false`)
97                // - we have gained the blocking short-term mutex lock
98                if !*locked && f(unsafe { &*self.inner.get() }) {
99                    *locked = true;
100
101                    Some(())
102                } else {
103                    None
104                }
105            })
106            .await;
107
108        IfMutexGuard { mutex: self }
109    }
110
111    /// Waits for the mutex to become unlocked and then executes the provided closure.
112    /// Will become ready only when the callback closure returns a `Some` result.
113    pub async fn with<F, R>(&self, mut f: F) -> R
114    where
115        F: FnMut(&mut T) -> Option<R>,
116    {
117        let result = self
118            .state
119            .wait(|locked| {
120                if !*locked {
121                    // Safety: it is safe to access the unsafe cell data, because:
122                    // - nobody holds the long term (async) lock on the mutex right now (`locked == false`)
123                    // - we have gained the blocking short-term mutex lock
124                    if let Some(result) = f(unsafe { &mut *self.inner.get() }) {
125                        *locked = true;
126                        return Some(result);
127                    }
128                }
129
130                None
131            })
132            .await;
133
134        // Construct and immediately drop the guard to unlock the mutex
135        let _ = IfMutexGuard { mutex: self };
136
137        result
138    }
139
140    /// Attempt to immediately lock the mutex.
141    pub fn try_lock(&self) -> Result<IfMutexGuard<'_, T, M>, TryLockError> {
142        self.try_lock_if(|_| true)
143    }
144
145    /// Attempt to immediately lock the mutex.
146    ///
147    /// If the mutex is already locked or the condition on the data is not true, this will return an error instead of waiting.
148    pub fn try_lock_if<F>(&self, mut f: F) -> Result<IfMutexGuard<'_, T, M>, TryLockError>
149    where
150        F: FnMut(&T) -> bool,
151    {
152        self.state.modify(|locked| {
153            if *locked {
154                (false, Err(TryLockError))
155            } else if f(unsafe { &*self.inner.get() }) {
156                // Safety: it is safe to access the unsafe cell data, because:
157                // - nobody holds the long term (async) lock on the mutex right now (`locked == false`)
158                // - we have gained the blocking short-term mutex lock
159                *locked = true;
160                (false, Ok(()))
161            } else {
162                (false, Err(TryLockError))
163            }
164        })?;
165
166        Ok(IfMutexGuard { mutex: self })
167    }
168
169    /// Consumes this mutex, returning the underlying data.
170    pub fn into_inner(self) -> T
171    where
172        T: Sized,
173    {
174        self.inner.into_inner()
175    }
176
177    /// Returns a mutable reference to the underlying data.
178    ///
179    /// Since this call borrows the Mutex mutably, no actual locking needs to
180    /// take place -- the mutable borrow statically guarantees no locks exist.
181    pub fn get_mut(&mut self) -> &mut T {
182        self.inner.get_mut()
183    }
184}
185
186/// Async mutex guard.
187///
188/// Owning an instance of this type indicates having
189/// successfully locked the mutex, and grants access to the contents.
190///
191/// Dropping it unlocks the mutex.
192pub struct IfMutexGuard<'a, T, M = MatterRawMutex>
193where
194    T: ?Sized,
195    M: RawMutex,
196{
197    mutex: &'a IfMutex<T, M>,
198}
199
200impl<T, M> Drop for IfMutexGuard<'_, T, M>
201where
202    T: ?Sized,
203    M: RawMutex,
204{
205    fn drop(&mut self) {
206        self.mutex.state.modify(|locked| {
207            assert!(*locked);
208
209            *locked = false;
210
211            (true, ())
212        })
213    }
214}
215
216impl<T, M> Deref for IfMutexGuard<'_, T, M>
217where
218    T: ?Sized,
219    M: RawMutex,
220{
221    type Target = T;
222
223    fn deref(&self) -> &Self::Target {
224        // Safety: the MutexGuard represents exclusive access to the contents
225        // of the mutex, so it's OK to get it.
226        unsafe { &*(self.mutex.inner.get() as *const T) }
227    }
228}
229
230impl<T, M> DerefMut for IfMutexGuard<'_, T, M>
231where
232    T: ?Sized,
233    M: RawMutex,
234{
235    fn deref_mut(&mut self) -> &mut Self::Target {
236        // Safety: the MutexGuard represents exclusive access to the contents
237        // of the mutex, so it's OK to get it.
238        unsafe { &mut *(self.mutex.inner.get()) }
239    }
240}