rs_matter/utils/sync/blocking.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
18pub use mutex::*;
19
20mod mutex {
21 //! A variation of the `embassy-sync` blocking mutex that allows in-place initialization
22 //! of the mutex with `Mutex::init(..) -> impl Init<Self>`.
23 //! Check `embassy_sync::blocking_mutex::Mutex` for the original implementation.
24
25 #![allow(clippy::should_implement_trait)]
26
27 use core::cell::UnsafeCell;
28
29 use embassy_sync::blocking_mutex::raw::RawMutex;
30
31 use crate::utils::{
32 init::{init, Init, UnsafeCellInit},
33 sync::blocking::raw::MatterRawMutex,
34 };
35
36 /// Blocking mutex (not async)
37 ///
38 /// Provides a blocking mutual exclusion primitive backed by an implementation of [`raw::RawMutex`].
39 ///
40 /// Which implementation you select depends on the context in which you're using the mutex, and you can choose which kind
41 /// of interior mutability fits your use case.
42 ///
43 /// Use [`CriticalSectionMutex`] when data can be shared between threads and interrupts.
44 ///
45 /// Use [`NoopMutex`] when data is only shared between tasks running on the same executor.
46 ///
47 /// Use [`ThreadModeMutex`] when data is shared between tasks running on the same executor but you want a global singleton.
48 ///
49 /// In all cases, the blocking mutex is intended to be short lived and not held across await points.
50 /// Use the async [`Mutex`](crate::mutex::Mutex) if you need a lock that is held across await points.
51 pub struct Mutex<T: ?Sized, R = MatterRawMutex> {
52 // NOTE: `raw` must be FIRST, so when using ThreadModeMutex the "can't drop in non-thread-mode" gets
53 // to run BEFORE dropping `data`.
54 raw: R,
55 data: UnsafeCell<T>,
56 }
57
58 unsafe impl<T: ?Sized + Send, R: RawMutex + Send> Send for Mutex<T, R> {}
59 unsafe impl<T: ?Sized + Send, R: RawMutex + Sync> Sync for Mutex<T, R> {}
60
61 impl<T, R: RawMutex> Mutex<T, R> {
62 /// Creates a new mutex in an unlocked state ready for use.
63 #[inline]
64 pub const fn new(val: T) -> Self {
65 Self {
66 raw: R::INIT,
67 data: UnsafeCell::new(val),
68 }
69 }
70
71 /// Creates a mutex in-place initializer in an unlocked state ready for use.
72 pub fn init<I: Init<T>>(val: I) -> impl Init<Self> {
73 init!(Self {
74 raw: R::INIT,
75 data <- UnsafeCell::init(val),
76 })
77 }
78
79 /// Creates a critical section and grants temporary access to the protected data.
80 #[inline(always)]
81 pub fn lock<U>(&self, f: impl FnOnce(&T) -> U) -> U {
82 self.raw.lock(|| {
83 let ptr = self.data.get() as *const T;
84 let inner = unsafe { &*ptr };
85 f(inner)
86 })
87 }
88 }
89
90 impl<T, R> Mutex<T, R> {
91 /// Creates a new mutex based on a pre-existing raw mutex.
92 ///
93 /// This allows creating a mutex in a constant context on stable Rust.
94 #[inline]
95 pub const fn const_new(raw_mutex: R, val: T) -> Self {
96 Self {
97 raw: raw_mutex,
98 data: UnsafeCell::new(val),
99 }
100 }
101
102 /// Consumes this mutex, returning the underlying data.
103 #[inline]
104 pub fn into_inner(self) -> T {
105 self.data.into_inner()
106 }
107
108 /// Returns a mutable reference to the underlying data.
109 ///
110 /// Since this call borrows the `Mutex` mutably, no actual locking needs to
111 /// take place---the mutable borrow statically guarantees no locks exist.
112 #[inline]
113 pub fn get_mut(&mut self) -> &mut T {
114 unsafe { &mut *self.data.get() }
115 }
116 }
117}
118
119pub mod raw {
120 /// The raw mutex used throughout the `rs-matter` codebase
121 #[cfg(not(feature = "sync-mutex"))]
122 pub type MatterRawMutex = embassy_sync::blocking_mutex::raw::NoopRawMutex;
123
124 /// The raw mutex used throughout the `rs-matter` codebase
125 #[cfg(all(feature = "sync-mutex", not(feature = "std")))]
126 pub type MatterRawMutex = embassy_sync::blocking_mutex::raw::CriticalSectionRawMutex;
127
128 /// The raw mutex used throughout the `rs-matter` codebase
129 #[cfg(all(feature = "sync-mutex", feature = "std"))]
130 pub type MatterRawMutex = StdRawMutex;
131
132 #[cfg(feature = "std")]
133 pub use std::*;
134
135 #[cfg(feature = "std")]
136 mod std {
137 use embassy_sync::blocking_mutex::raw::RawMutex;
138
139 /// An `embassy-sync` `RawMutex` implementation using `std::sync::Mutex`.
140 // TODO: Upstream into `embassy-sync` itself.
141 #[derive(Default)]
142 pub struct StdRawMutex(std::sync::Mutex<()>);
143
144 impl StdRawMutex {
145 pub const fn new() -> Self {
146 Self(std::sync::Mutex::new(()))
147 }
148 }
149
150 unsafe impl RawMutex for StdRawMutex {
151 #[allow(clippy::declare_interior_mutable_const)]
152 const INIT: Self = StdRawMutex(std::sync::Mutex::new(()));
153
154 #[inline(always)]
155 fn lock<R>(&self, f: impl FnOnce() -> R) -> R {
156 let _guard = unwrap!(self.0.lock(), "Mutex lock failed");
157
158 f()
159 }
160 }
161 }
162}