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}