Skip to main content

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}