nstd_sys/os/unix/
mutex.rs

1//! A mutual exclusion primitive useful for protecting shared data.
2use crate::{
3    core::{optional::NSTDOptional, result::NSTDResult, time::NSTDDuration},
4    heap_ptr::{
5        nstd_heap_ptr_drop, nstd_heap_ptr_get, nstd_heap_ptr_get_mut, NSTDHeapPtr,
6        NSTDOptionalHeapPtr,
7    },
8    thread::nstd_thread_is_panicking,
9    NSTDAny, NSTDAnyMut, NSTDBool, NSTD_FALSE, NSTD_TRUE,
10};
11use core::{
12    cell::{Cell, UnsafeCell},
13    marker::PhantomData,
14    mem::MaybeUninit,
15};
16use libc::{
17    pthread_mutex_destroy, pthread_mutex_init, pthread_mutex_lock, pthread_mutex_t,
18    pthread_mutex_trylock, pthread_mutex_unlock, pthread_mutexattr_destroy, pthread_mutexattr_init,
19    pthread_mutexattr_settype, pthread_mutexattr_t, PTHREAD_MUTEX_INITIALIZER,
20    PTHREAD_MUTEX_NORMAL,
21};
22use nstdapi::nstdapi;
23
24/// A raw mutex wrapping `pthread_mutex_t`.
25///
26/// This type has the same in-memory representation as `pthread_mutex_t`.
27#[repr(transparent)]
28struct RawMutex(UnsafeCell<pthread_mutex_t>);
29impl Drop for RawMutex {
30    /// [`RawMutex`]'s destructor.
31    fn drop(&mut self) {
32        // SAFETY: Destroying a locked mutex results in undefined behavior, so here we check if the
33        // mutex is locked. If the mutex *is* locked then it's guard must have been leaked, in this
34        // case we will leak the raw mutex data as well.
35        unsafe {
36            if pthread_mutex_trylock(self.0.get()) == 0 {
37                // This shall only fail if the mutex is either robust,
38                // `PTHREAD_MUTEX_ERRORCHECK`, or `PTHREAD_MUTEX_RECURSIVE` and the thread does
39                // not own the mutex.
40                pthread_mutex_unlock(self.0.get());
41                pthread_mutex_destroy(self.0.get());
42            }
43        }
44    }
45}
46
47/// A mutex attribute builder.
48struct MutexAttrs(pthread_mutexattr_t);
49impl MutexAttrs {
50    /// Creates a new instance of [`MutexAttrs`].
51    fn new() -> Option<Self> {
52        let mut attr = MaybeUninit::uninit();
53        // SAFETY: All operations are thread-safe, errors are checked.
54        unsafe {
55            if pthread_mutexattr_init(attr.as_mut_ptr()) == 0 {
56                // This shall never fail, PTHREAD_MUTEX_NORMAL is a valid type.
57                pthread_mutexattr_settype(attr.as_mut_ptr(), PTHREAD_MUTEX_NORMAL);
58                return Some(Self(attr.assume_init()));
59            }
60        }
61        None
62    }
63}
64impl Drop for MutexAttrs {
65    /// [`MutexAttrs`] destructor.
66    #[inline]
67    fn drop(&mut self) {
68        // SAFETY: Rust's type system will ensure that `Self` is properly initialized.
69        unsafe { pthread_mutexattr_destroy(&mut self.0) };
70    }
71}
72
73/// A mutual exclusion primitive useful for protecting shared data.
74#[nstdapi]
75pub struct NSTDUnixMutex<'a> {
76    /// The underlying mutex.
77    inner: RawMutex,
78    /// The protected data.
79    data: UnsafeCell<NSTDHeapPtr<'a>>,
80    /// Determines whether or not the mutex is poisoned.
81    poisoned: Cell<NSTDBool>,
82}
83/// # Safety
84///
85/// The data that the mutex is protecting must be able to be safely sent between threads.
86// SAFETY: The user guarantees that the data is thread-safe.
87unsafe impl Send for NSTDUnixMutex<'_> {}
88/// # Safety
89///
90/// The data that the mutex is protecting must be able to be safely shared between threads.
91// SAFETY: The user guarantees that the data is thread-safe.
92unsafe impl Sync for NSTDUnixMutex<'_> {}
93
94/// Represents an optional value of type `NSTDUnixMutex`.
95pub type NSTDUnixOptionalMutex<'a> = NSTDOptional<NSTDUnixMutex<'a>>;
96
97/// A handle to a mutex's protected data.
98#[nstdapi]
99pub struct NSTDUnixMutexGuard<'m, 'a> {
100    /// A reference to the mutex.
101    mutex: &'m NSTDUnixMutex<'a>,
102    /// Ensures that the guard is not [Send].
103    pd: PhantomData<*const ()>,
104}
105impl<'m, 'a> NSTDUnixMutexGuard<'m, 'a> {
106    /// Constructs a new mutex guard.
107    #[inline]
108    const fn new(mutex: &'m NSTDUnixMutex<'a>) -> Self {
109        Self {
110            mutex,
111            pd: PhantomData,
112        }
113    }
114}
115impl Drop for NSTDUnixMutexGuard<'_, '_> {
116    /// Drops the guard, releasing the lock for the mutex.
117    fn drop(&mut self) {
118        #[allow(unused_unsafe)]
119        // SAFETY: This operation is safe.
120        if unsafe { nstd_thread_is_panicking() } {
121            self.mutex.poisoned.set(NSTD_TRUE);
122        }
123        // SAFETY: `self` has a valid reference to the mutex.
124        // This shall only fail if the mutex is either robust, `PTHREAD_MUTEX_ERRORCHECK`, or
125        // `PTHREAD_MUTEX_RECURSIVE` and the thread does not own the mutex.
126        unsafe { pthread_mutex_unlock(self.mutex.inner.0.get()) };
127    }
128}
129/// # Safety
130///
131/// The data that the guard is protecting must be able to be safely shared between threads.
132// SAFETY: The user guarantees that the data is thread-safe.
133unsafe impl Sync for NSTDUnixMutexGuard<'_, '_> {}
134
135/// A result type returned from `nstd_os_unix_mutex_lock` containing the mutex guard whether or not
136/// the data is poisoned.
137pub type NSTDUnixMutexLockResult<'m, 'a> =
138    NSTDResult<NSTDUnixMutexGuard<'m, 'a>, NSTDUnixMutexGuard<'m, 'a>>;
139
140/// An optional value of type `NSTDUnixMutexLockResult`.
141///
142/// This type is returned from the `nstd_os_unix_mutex_try_lock` where the uninitialized variant
143/// means that the function would block.
144pub type NSTDUnixOptionalMutexLockResult<'m, 'a> = NSTDOptional<NSTDUnixMutexLockResult<'m, 'a>>;
145
146/// Creates a new mutex in an unlocked state.
147///
148/// # Parameters:
149///
150/// - `NSTDHeapPtr data` - The data to be protected by the mutex.
151///
152/// # Returns
153///
154/// `NSTDUnixOptionalMutex mutex` - The new initialized mutex on success, or an uninitialized "none"
155/// value if the OS was unable to create and initialize the mutex.
156#[nstdapi]
157pub fn nstd_os_unix_mutex_new(data: NSTDHeapPtr<'_>) -> NSTDUnixOptionalMutex<'_> {
158    let mutex = RawMutex(UnsafeCell::new(PTHREAD_MUTEX_INITIALIZER));
159    if let Some(attrs) = MutexAttrs::new() {
160        // SAFETY: `attrs` is properly initialized.
161        if unsafe { pthread_mutex_init(mutex.0.get(), &attrs.0) } == 0 {
162            return NSTDOptional::Some(NSTDUnixMutex {
163                inner: mutex,
164                data: UnsafeCell::new(data),
165                poisoned: Cell::new(NSTD_FALSE),
166            });
167        }
168    }
169    NSTDOptional::None
170}
171
172/// Returns a Unix mutex's native OS handle.
173///
174/// # Parameters:
175///
176/// - `const NSTDUnixMutex *mutex` - The mutex.
177///
178/// # Returns
179///
180/// `pthread_mutex_t raw` - The native mutex handle.
181#[inline]
182#[nstdapi]
183pub fn nstd_os_unix_mutex_handle(mutex: &NSTDUnixMutex<'_>) -> pthread_mutex_t {
184    // SAFETY: `mutex` is behind an initialized reference.
185    unsafe { *mutex.inner.0.get() }
186}
187
188/// Determines whether or not a mutex's data is poisoned.
189///
190/// # Parameters:
191///
192/// - `const NSTDUnixMutex *mutex` - The mutex to check.
193///
194/// # Returns
195///
196/// `NSTDBool is_poisoned` - `NSTD_TRUE` if the mutex's data is poisoned.
197#[inline]
198#[nstdapi]
199pub fn nstd_os_unix_mutex_is_poisoned(mutex: &NSTDUnixMutex<'_>) -> NSTDBool {
200    mutex.poisoned.get()
201}
202
203/// Waits for a mutex lock to become acquired, returning a guard wrapping the protected data.
204///
205/// # Parameters:
206///
207/// - `const NSTDUnixMutex *mutex` - The mutex to lock.
208///
209/// # Returns
210///
211/// `NSTDUnixOptionalMutexLockResult guard` - A handle to the mutex's protected data on success, or
212/// an uninitialized "none" value if the OS failed to lock the mutex.
213#[nstdapi]
214pub fn nstd_os_unix_mutex_lock<'m, 'a>(
215    mutex: &'m NSTDUnixMutex<'a>,
216) -> NSTDUnixOptionalMutexLockResult<'m, 'a> {
217    // SAFETY: `mutex` is behind an initialized reference.
218    if unsafe { pthread_mutex_lock(mutex.inner.0.get()) } == 0 {
219        let guard = NSTDUnixMutexGuard::new(mutex);
220        return NSTDOptional::Some(match mutex.poisoned.get() {
221            true => NSTDResult::Err(guard),
222            false => NSTDResult::Ok(guard),
223        });
224    }
225    NSTDOptional::None
226}
227
228/// The non-blocking variant of `nstd_os_unix_mutex_lock`. This will return immediately with an
229/// uninitialized "none" value if the mutex is locked.
230///
231/// # Note
232///
233/// This operation may return a "none" value in the case that the OS fails to lock the mutex.
234///
235/// # Parameters:
236///
237/// - `const NSTDUnixMutex *mutex` - The mutex to lock.
238///
239/// # Returns
240///
241/// `NSTDUnixOptionalMutexLockResult guard` - A handle to the mutex's data, or "none" if the mutex
242/// is locked.
243#[nstdapi]
244pub fn nstd_os_unix_mutex_try_lock<'m, 'a>(
245    mutex: &'m NSTDUnixMutex<'a>,
246) -> NSTDUnixOptionalMutexLockResult<'m, 'a> {
247    // SAFETY: `mutex` is behind an initialized reference.
248    if unsafe { pthread_mutex_trylock(mutex.inner.0.get()) } == 0 {
249        let guard = NSTDUnixMutexGuard::new(mutex);
250        return NSTDOptional::Some(match mutex.poisoned.get() {
251            true => NSTDResult::Err(guard),
252            false => NSTDResult::Ok(guard),
253        });
254    }
255    NSTDOptional::None
256}
257
258/// The timed variant of `nstd_os_unix_mutex_lock`. This will return with an uninitialized "none"
259/// value if the mutex remains locked for the time span of `duration`.
260///
261/// # Notes
262///
263/// This operation may return a "none" value in the case that the OS fails to lock the mutex.
264///
265/// This function will return immediately with a "none" value on unsupported platforms.
266/// Supported platforms include Android, DragonFly BSD, FreeBSD, NetBSD, OpenBSD, Haiku, illumos,
267/// Linux, QNX Neutrino, and Oracle Solaris.
268///
269/// # Parameters:
270///
271/// - `const NSTDUnixMutex *mutex` - The mutex to lock.
272///
273/// - `NSTDDuration duration` - The amount of time to block for.
274///
275/// # Returns
276///
277/// `NSTDUnixOptionalMutexLockResult guard` - A handle to the mutex's data, or "none" if the mutex
278/// remains locked for the time span of `duration`.
279#[nstdapi]
280#[allow(unused_variables, clippy::doc_markdown, clippy::missing_const_for_fn)]
281pub fn nstd_os_unix_mutex_timed_lock<'m, 'a>(
282    mutex: &'m NSTDUnixMutex<'a>,
283    duration: NSTDDuration,
284) -> NSTDUnixOptionalMutexLockResult<'m, 'a> {
285    #[cfg(any(
286        target_os = "android",
287        target_os = "dragonfly",
288        target_os = "freebsd",
289        target_os = "haiku",
290        target_os = "illumos",
291        target_os = "linux",
292        target_os = "netbsd",
293        target_os = "nto",
294        target_os = "openbsd",
295        target_os = "solaris"
296    ))]
297    {
298        use crate::os::unix::time::{
299            nstd_os_unix_time_add, nstd_os_unix_time_nanoseconds, nstd_os_unix_time_now,
300            nstd_os_unix_time_seconds,
301        };
302        use libc::{pthread_mutex_timedlock, timespec};
303        if let NSTDOptional::Some(mut time) = nstd_os_unix_time_now() {
304            time = nstd_os_unix_time_add(time, duration);
305            #[allow(trivial_numeric_casts)]
306            let duration = timespec {
307                tv_sec: nstd_os_unix_time_seconds(time) as _,
308                tv_nsec: nstd_os_unix_time_nanoseconds(time).into(),
309            };
310            // SAFETY: `mutex` is behind an initialized reference.
311            if unsafe { pthread_mutex_timedlock(mutex.inner.0.get(), &duration) } == 0 {
312                let guard = NSTDUnixMutexGuard::new(mutex);
313                return NSTDOptional::Some(match mutex.poisoned.get() {
314                    true => NSTDResult::Err(guard),
315                    false => NSTDResult::Ok(guard),
316                });
317            }
318        }
319    }
320    NSTDOptional::None
321}
322
323/// Returns a pointer to a mutex's raw data.
324///
325/// # Parameters:
326///
327/// - `const NSTDUnixMutexGuard *guard` - A handle to the mutex's protected data.
328///
329/// # Returns
330///
331/// `NSTDAny data` - A pointer to the mutex's data.
332#[inline]
333#[nstdapi]
334#[allow(clippy::missing_const_for_fn)]
335pub fn nstd_os_unix_mutex_get(guard: &NSTDUnixMutexGuard<'_, '_>) -> NSTDAny {
336    // SAFETY: `mutex` is behind a valid reference.
337    nstd_heap_ptr_get(unsafe { &*guard.mutex.data.get() })
338}
339
340/// Returns a mutable pointer to a mutex's raw data.
341///
342/// # Parameters:
343///
344/// - `NSTDUnixMutexGuard *guard` - A handle to the mutex's protected data.
345///
346/// # Returns
347///
348/// `NSTDAnyMut data` - A pointer to the mutex's data.
349#[inline]
350#[nstdapi]
351pub fn nstd_os_unix_mutex_get_mut(guard: &mut NSTDUnixMutexGuard<'_, '_>) -> NSTDAnyMut {
352    // SAFETY: `mutex` is behind a valid reference.
353    nstd_heap_ptr_get_mut(unsafe { &mut *guard.mutex.data.get() })
354}
355
356/// Consumes a mutex and returns the data it was protecting.
357///
358/// # Parameters:
359///
360/// - `NSTDUnixMutex mutex` - The mutex to take ownership of.
361///
362/// # Returns
363///
364/// `NSTDOptionalHeapPtr data` - Ownership of the mutex's data, or an uninitialized "none" variant
365/// if the mutex was poisoned.
366#[inline]
367#[nstdapi]
368pub fn nstd_os_unix_mutex_into_inner(mutex: NSTDUnixMutex<'_>) -> NSTDOptionalHeapPtr<'_> {
369    match nstd_os_unix_mutex_is_poisoned(&mutex) {
370        false => NSTDOptional::Some(mutex.data.into_inner()),
371        true => NSTDOptional::None,
372    }
373}
374
375/// Unlocks a mutex by consuming it's guard.
376///
377/// # Parameters:
378///
379/// - `NSTDUnixMutexGuard guard` - The mutex guard to take ownership of.
380#[inline]
381#[nstdapi]
382#[allow(
383    unused_variables,
384    clippy::missing_const_for_fn,
385    clippy::needless_pass_by_value
386)]
387pub fn nstd_os_unix_mutex_unlock(guard: NSTDUnixMutexGuard<'_, '_>) {}
388
389/// Frees an instance of `NSTDUnixMutex`.
390///
391/// # Parameters:
392///
393/// - `NSTDUnixMutex mutex` - The mutex to free.
394#[inline]
395#[nstdapi]
396#[allow(
397    unused_variables,
398    clippy::missing_const_for_fn,
399    clippy::needless_pass_by_value
400)]
401pub fn nstd_os_unix_mutex_free(mutex: NSTDUnixMutex<'_>) {}
402
403/// Frees an instance of `NSTDUnixMutex` after invoking `callback` with the mutex's data.
404///
405/// `callback` will not be called if the mutex is poisoned.
406///
407/// # Parameters:
408///
409/// - `NSTDUnixMutex mutex` - The mutex to free.
410///
411/// - `void (*callback)(NSTDAnyMut)` - The mutex data's destructor.
412///
413/// # Safety
414///
415/// This operation makes a direct call on a C function pointer (`callback`).
416#[inline]
417#[nstdapi]
418pub unsafe fn nstd_os_unix_mutex_drop(
419    mutex: NSTDUnixMutex<'_>,
420    callback: unsafe extern "C" fn(NSTDAnyMut),
421) {
422    if !mutex.poisoned.get() {
423        nstd_heap_ptr_drop(mutex.data.into_inner(), callback);
424    }
425}