use crate::{
core::{optional::NSTDOptional, result::NSTDResult, time::NSTDDuration},
heap_ptr::{
nstd_heap_ptr_drop, nstd_heap_ptr_get, nstd_heap_ptr_get_mut, NSTDHeapPtr,
NSTDOptionalHeapPtr,
},
thread::nstd_thread_is_panicking,
NSTDAny, NSTDAnyMut, NSTDBool, NSTD_FALSE, NSTD_TRUE,
};
use core::{
cell::{Cell, UnsafeCell},
marker::PhantomData,
mem::MaybeUninit,
};
use libc::{
pthread_mutex_destroy, pthread_mutex_init, pthread_mutex_lock, pthread_mutex_t,
pthread_mutex_trylock, pthread_mutex_unlock, pthread_mutexattr_destroy, pthread_mutexattr_init,
pthread_mutexattr_settype, pthread_mutexattr_t, PTHREAD_MUTEX_INITIALIZER,
PTHREAD_MUTEX_NORMAL,
};
use nstdapi::nstdapi;
#[repr(transparent)]
struct RawMutex(UnsafeCell<pthread_mutex_t>);
impl Drop for RawMutex {
fn drop(&mut self) {
unsafe {
if pthread_mutex_trylock(self.0.get()) == 0 {
pthread_mutex_unlock(self.0.get());
pthread_mutex_destroy(self.0.get());
}
}
}
}
struct MutexAttrs(pthread_mutexattr_t);
impl MutexAttrs {
fn new() -> Option<Self> {
let mut attr = MaybeUninit::uninit();
unsafe {
if pthread_mutexattr_init(attr.as_mut_ptr()) == 0 {
pthread_mutexattr_settype(attr.as_mut_ptr(), PTHREAD_MUTEX_NORMAL);
return Some(Self(attr.assume_init()));
}
}
None
}
}
impl Drop for MutexAttrs {
#[inline]
fn drop(&mut self) {
unsafe { pthread_mutexattr_destroy(&mut self.0) };
}
}
#[nstdapi]
pub struct NSTDUnixMutex<'a> {
inner: RawMutex,
data: UnsafeCell<NSTDHeapPtr<'a>>,
poisoned: Cell<NSTDBool>,
}
unsafe impl Send for NSTDUnixMutex<'_> {}
unsafe impl Sync for NSTDUnixMutex<'_> {}
pub type NSTDUnixOptionalMutex<'a> = NSTDOptional<NSTDUnixMutex<'a>>;
#[nstdapi]
pub struct NSTDUnixMutexGuard<'m, 'a> {
mutex: &'m NSTDUnixMutex<'a>,
pd: PhantomData<*const ()>,
}
impl<'m, 'a> NSTDUnixMutexGuard<'m, 'a> {
#[inline]
const fn new(mutex: &'m NSTDUnixMutex<'a>) -> Self {
Self {
mutex,
pd: PhantomData,
}
}
}
impl Drop for NSTDUnixMutexGuard<'_, '_> {
fn drop(&mut self) {
#[allow(unused_unsafe)]
if unsafe { nstd_thread_is_panicking() } {
self.mutex.poisoned.set(NSTD_TRUE);
}
unsafe { pthread_mutex_unlock(self.mutex.inner.0.get()) };
}
}
unsafe impl Sync for NSTDUnixMutexGuard<'_, '_> {}
pub type NSTDUnixMutexLockResult<'m, 'a> =
NSTDResult<NSTDUnixMutexGuard<'m, 'a>, NSTDUnixMutexGuard<'m, 'a>>;
pub type NSTDUnixOptionalMutexLockResult<'m, 'a> = NSTDOptional<NSTDUnixMutexLockResult<'m, 'a>>;
#[nstdapi]
pub fn nstd_os_unix_mutex_new(data: NSTDHeapPtr<'_>) -> NSTDUnixOptionalMutex<'_> {
let mutex = RawMutex(UnsafeCell::new(PTHREAD_MUTEX_INITIALIZER));
if let Some(attrs) = MutexAttrs::new() {
if unsafe { pthread_mutex_init(mutex.0.get(), &attrs.0) } == 0 {
return NSTDOptional::Some(NSTDUnixMutex {
inner: mutex,
data: UnsafeCell::new(data),
poisoned: Cell::new(NSTD_FALSE),
});
}
}
NSTDOptional::None
}
#[inline]
#[nstdapi]
pub fn nstd_os_unix_mutex_handle(mutex: &NSTDUnixMutex<'_>) -> pthread_mutex_t {
unsafe { *mutex.inner.0.get() }
}
#[inline]
#[nstdapi]
pub fn nstd_os_unix_mutex_is_poisoned(mutex: &NSTDUnixMutex<'_>) -> NSTDBool {
mutex.poisoned.get()
}
#[nstdapi]
pub fn nstd_os_unix_mutex_lock<'m, 'a>(
mutex: &'m NSTDUnixMutex<'a>,
) -> NSTDUnixOptionalMutexLockResult<'m, 'a> {
if unsafe { pthread_mutex_lock(mutex.inner.0.get()) } == 0 {
let guard = NSTDUnixMutexGuard::new(mutex);
return NSTDOptional::Some(match mutex.poisoned.get() {
true => NSTDResult::Err(guard),
false => NSTDResult::Ok(guard),
});
}
NSTDOptional::None
}
#[nstdapi]
pub fn nstd_os_unix_mutex_try_lock<'m, 'a>(
mutex: &'m NSTDUnixMutex<'a>,
) -> NSTDUnixOptionalMutexLockResult<'m, 'a> {
if unsafe { pthread_mutex_trylock(mutex.inner.0.get()) } == 0 {
let guard = NSTDUnixMutexGuard::new(mutex);
return NSTDOptional::Some(match mutex.poisoned.get() {
true => NSTDResult::Err(guard),
false => NSTDResult::Ok(guard),
});
}
NSTDOptional::None
}
#[nstdapi]
#[allow(unused_variables, clippy::doc_markdown, clippy::missing_const_for_fn)]
pub fn nstd_os_unix_mutex_timed_lock<'m, 'a>(
mutex: &'m NSTDUnixMutex<'a>,
duration: NSTDDuration,
) -> NSTDUnixOptionalMutexLockResult<'m, 'a> {
#[cfg(any(
target_os = "android",
target_os = "dragonfly",
target_os = "freebsd",
target_os = "haiku",
target_os = "illumos",
target_os = "linux",
target_os = "netbsd",
target_os = "nto",
target_os = "openbsd",
target_os = "solaris"
))]
{
use crate::os::unix::time::{
nstd_os_unix_time_add, nstd_os_unix_time_nanoseconds, nstd_os_unix_time_now,
nstd_os_unix_time_seconds,
};
use libc::{pthread_mutex_timedlock, timespec};
if let NSTDOptional::Some(mut time) = nstd_os_unix_time_now() {
time = nstd_os_unix_time_add(time, duration);
#[allow(trivial_numeric_casts)]
let duration = timespec {
tv_sec: nstd_os_unix_time_seconds(time) as _,
tv_nsec: nstd_os_unix_time_nanoseconds(time).into(),
};
if unsafe { pthread_mutex_timedlock(mutex.inner.0.get(), &duration) } == 0 {
let guard = NSTDUnixMutexGuard::new(mutex);
return NSTDOptional::Some(match mutex.poisoned.get() {
true => NSTDResult::Err(guard),
false => NSTDResult::Ok(guard),
});
}
}
}
NSTDOptional::None
}
#[inline]
#[nstdapi]
#[allow(clippy::missing_const_for_fn)]
pub fn nstd_os_unix_mutex_get(guard: &NSTDUnixMutexGuard<'_, '_>) -> NSTDAny {
nstd_heap_ptr_get(unsafe { &*guard.mutex.data.get() })
}
#[inline]
#[nstdapi]
pub fn nstd_os_unix_mutex_get_mut(guard: &mut NSTDUnixMutexGuard<'_, '_>) -> NSTDAnyMut {
nstd_heap_ptr_get_mut(unsafe { &mut *guard.mutex.data.get() })
}
#[inline]
#[nstdapi]
pub fn nstd_os_unix_mutex_into_inner(mutex: NSTDUnixMutex<'_>) -> NSTDOptionalHeapPtr<'_> {
match nstd_os_unix_mutex_is_poisoned(&mutex) {
false => NSTDOptional::Some(mutex.data.into_inner()),
true => NSTDOptional::None,
}
}
#[inline]
#[nstdapi]
#[allow(
unused_variables,
clippy::missing_const_for_fn,
clippy::needless_pass_by_value
)]
pub fn nstd_os_unix_mutex_unlock(guard: NSTDUnixMutexGuard<'_, '_>) {}
#[inline]
#[nstdapi]
#[allow(
unused_variables,
clippy::missing_const_for_fn,
clippy::needless_pass_by_value
)]
pub fn nstd_os_unix_mutex_free(mutex: NSTDUnixMutex<'_>) {}
#[inline]
#[nstdapi]
pub unsafe fn nstd_os_unix_mutex_drop(
mutex: NSTDUnixMutex<'_>,
callback: unsafe extern "C" fn(NSTDAnyMut),
) {
if !mutex.poisoned.get() {
nstd_heap_ptr_drop(mutex.data.into_inner(), callback);
}
}