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}