Skip to main content

cubecl_environment/sync/
base.rs

1#[cfg(not(feature = "std"))]
2use spin::{Mutex as MutexImported, MutexGuard, Once as OnceImported, RwLock as RwLockImported};
3#[cfg(feature = "std")]
4use std::sync::{
5    Mutex as MutexImported, MutexGuard, OnceLock as OnceImported, RwLock as RwLockImported,
6};
7
8#[cfg(not(feature = "std"))]
9pub use spin::{LazyLock, RwLockReadGuard, RwLockWriteGuard};
10#[cfg(feature = "std")]
11pub use std::sync::{LazyLock, RwLockReadGuard, RwLockWriteGuard};
12
13/// A spin-based one-time initialization cell, identical on every target.
14///
15/// Prefer [`SyncOnceCell`] for plain lazy initialization; use this when the
16/// fallible [`spin::Once::try_call_once`] API is needed.
17pub use spin::Once;
18
19#[cfg(target_has_atomic = "ptr")]
20pub use alloc::sync::Arc;
21#[cfg(not(target_has_atomic = "ptr"))]
22pub use portable_atomic_util::Arc;
23
24#[cfg(target_has_atomic = "ptr")]
25pub use core::sync::atomic::{AtomicBool, AtomicU8, AtomicU32, AtomicUsize, Ordering};
26#[cfg(not(target_has_atomic = "ptr"))]
27pub use portable_atomic::{AtomicBool, AtomicU8, AtomicU32, AtomicUsize, Ordering};
28
29// No `AtomicU64`: `target_has_atomic = "ptr"` says nothing about 64-bit
30// atomics, and a 32-bit target can have pointer-width CAS without them
31// (thumbv7m). Its one user is behind `stream_local`, which is `std`, and every
32// std target has them — so it takes `core`'s directly rather than making this
33// shim carry a width it cannot provide on both arms.
34
35/// A mutual exclusion primitive useful for protecting shared data
36///
37/// This mutex will block threads waiting for the lock to become available. The
38/// mutex can also be statically initialized or created via a [`Mutex::new`]
39///
40/// [Mutex] wrapper to make `spin::Mutex` API compatible with `std::sync::Mutex` to swap
41#[derive(Debug, Default)]
42pub struct Mutex<T: ?Sized> {
43    inner: MutexImported<T>,
44}
45
46impl<T> Mutex<T> {
47    /// Creates a new mutex in an unlocked state ready for use.
48    #[inline(always)]
49    pub const fn new(value: T) -> Self {
50        Self {
51            inner: MutexImported::new(value),
52        }
53    }
54
55    /// Consumes the mutex, returning the value it protected. A poisoned lock
56    /// is recovered, as in [`lock`](Self::lock).
57    #[inline(always)]
58    pub fn into_inner(self) -> T {
59        #[cfg(not(feature = "std"))]
60        {
61            self.inner.into_inner()
62        }
63
64        #[cfg(feature = "std")]
65        {
66            self.inner
67                .into_inner()
68                .unwrap_or_else(|poisoned| poisoned.into_inner())
69        }
70    }
71}
72
73impl<T: ?Sized> Mutex<T> {
74    /// Locks the mutex blocking the current thread until it is able to do so.
75    ///
76    /// Locking cannot fail, so no `Result` is returned. A poisoned lock is
77    /// recovered rather than reported: a panic under a guard must not turn
78    /// every later lock into a panic, matching the unwind behavior of the spin
79    /// implementation used off-std.
80    #[inline(always)]
81    pub fn lock(&self) -> MutexGuard<'_, T> {
82        #[cfg(not(feature = "std"))]
83        {
84            self.inner.lock()
85        }
86
87        #[cfg(feature = "std")]
88        {
89            self.inner
90                .lock()
91                .unwrap_or_else(|poisoned| poisoned.into_inner())
92        }
93    }
94}
95
96/// A reader-writer lock which is exclusively locked for writing or shared for reading.
97/// This reader-writer lock will block threads waiting for the lock to become available.
98/// The lock can also be statically initialized or created via a [`RwLock::new`]
99/// [`RwLock`] wrapper to make `spin::RwLock` API compatible with `std::sync::RwLock` to swap
100#[derive(Debug)]
101pub struct RwLock<T: ?Sized> {
102    inner: RwLockImported<T>,
103}
104
105impl<T> RwLock<T> {
106    /// Creates a new reader-writer lock in an unlocked state ready for use.
107    #[inline(always)]
108    pub const fn new(value: T) -> Self {
109        Self {
110            inner: RwLockImported::new(value),
111        }
112    }
113}
114
115impl<T: ?Sized> RwLock<T> {
116    /// Locks this rwlock with shared read access, blocking the current thread
117    /// until it can be acquired.
118    ///
119    /// Poisoning is recovered, never reported; see [`Mutex::lock`].
120    #[inline(always)]
121    pub fn read(&self) -> RwLockReadGuard<'_, T> {
122        #[cfg(not(feature = "std"))]
123        {
124            self.inner.read()
125        }
126        #[cfg(feature = "std")]
127        {
128            self.inner
129                .read()
130                .unwrap_or_else(|poisoned| poisoned.into_inner())
131        }
132    }
133
134    /// Locks this rwlock with exclusive write access, blocking the current thread
135    /// until it can be acquired.
136    ///
137    /// Poisoning is recovered, never reported; see [`Mutex::lock`].
138    #[inline(always)]
139    pub fn write(&self) -> RwLockWriteGuard<'_, T> {
140        #[cfg(not(feature = "std"))]
141        {
142            self.inner.write()
143        }
144
145        #[cfg(feature = "std")]
146        {
147            self.inner
148                .write()
149                .unwrap_or_else(|poisoned| poisoned.into_inner())
150        }
151    }
152}
153
154/// An opaque thread identifier.
155///
156/// This is a stub when no std is available to swap with `std::thread::ThreadId`.
157/// There is no way to obtain one on such targets, so it exists only to keep
158/// thread-keyed types nameable.
159#[allow(dead_code)]
160#[derive(Eq, PartialEq, Clone, Copy, Hash, Debug)]
161pub struct ThreadId(core::num::NonZeroU64);
162
163/// A cell that provides lazy one-time initialization that implements [Sync] and [Send].
164///
165/// This module is a stub when no std is available to swap with [`std::sync::OnceLock`].
166pub struct SyncOnceCell<T>(OnceImported<T>);
167
168impl<T> Default for SyncOnceCell<T> {
169    fn default() -> Self {
170        Self::new()
171    }
172}
173
174impl<T> SyncOnceCell<T> {
175    /// Create a new once.
176    #[inline(always)]
177    pub fn new() -> Self {
178        Self(OnceImported::new())
179    }
180
181    /// Initialize the cell with a value.
182    #[inline(always)]
183    pub fn initialized(value: T) -> Self {
184        #[cfg(not(feature = "std"))]
185        {
186            let cell = OnceImported::initialized(value);
187            Self(cell)
188        }
189
190        #[cfg(feature = "std")]
191        {
192            let cell = OnceImported::new();
193            // Infallible: the cell was just created, so it is empty. Ignoring
194            // the `Err` is what keeps `T: Debug` off this whole impl.
195            let _ = cell.set(value);
196
197            Self(cell)
198        }
199    }
200
201    /// Gets the contents of the cell, initializing it with `f` if the cell
202    /// was empty.
203    #[inline(always)]
204    pub fn get_or_init<F>(&self, f: F) -> &T
205    where
206        F: FnOnce() -> T,
207    {
208        #[cfg(not(feature = "std"))]
209        {
210            self.0.call_once(f)
211        }
212
213        #[cfg(feature = "std")]
214        {
215            self.0.get_or_init(f)
216        }
217    }
218}
219
220#[cfg(all(test, feature = "std"))]
221mod tests {
222    use super::*;
223
224    /// Regression: a panic under a guard must not poison the lock — one
225    /// failed autotune must not take down every later kernel launch.
226    #[test]
227    fn poisoned_mutex_recovers() {
228        let mutex = Mutex::new(0u32);
229
230        std::panic::catch_unwind(std::panic::AssertUnwindSafe(|| {
231            let _guard = mutex.lock();
232            panic!("poison the lock");
233        }))
234        .unwrap_err();
235
236        *mutex.lock() += 1;
237        assert_eq!(*mutex.lock(), 1);
238    }
239
240    #[test]
241    fn poisoned_rwlock_recovers() {
242        let lock = RwLock::new(0u32);
243
244        std::panic::catch_unwind(std::panic::AssertUnwindSafe(|| {
245            let _guard = lock.write();
246            panic!("poison the lock");
247        }))
248        .unwrap_err();
249
250        assert_eq!(*lock.read(), 0);
251    }
252
253    #[test]
254    fn a_mutex_locks_a_trait_object() {
255        let mutex: Arc<Mutex<dyn Iterator<Item = u32> + Send>> = Arc::new(Mutex::new(0..3));
256
257        assert_eq!(mutex.lock().next(), Some(0));
258        assert_eq!(mutex.lock().next(), Some(1));
259    }
260
261    #[test]
262    fn an_rwlock_locks_a_trait_object() {
263        let lock: Arc<RwLock<dyn Iterator<Item = u32> + Send + Sync>> = Arc::new(RwLock::new(0..3));
264
265        assert_eq!(lock.write().next(), Some(0));
266        assert_eq!(lock.read().size_hint(), (2, Some(2)));
267    }
268}