use core::cell::UnsafeCell; use core::ops::Deref; use alloc::boxed::Box; use libc::{ pthread_mutex_lock, pthread_mutex_t, pthread_mutex_trylock, pthread_mutex_unlock, EBUSY, PTHREAD_MUTEX_INITIALIZER, }; use crate::lazy_box::{LazyBox, LazyInit}; struct RawMutex(UnsafeCell); impl LazyInit for RawMutex { fn init() -> Box { // We need to box this no matter what, because copying a pthread mutex // results in undocumented behavior. let mutex = Box::new(Self(UnsafeCell::new(PTHREAD_MUTEX_INITIALIZER))); if !cfg!(feature = "unsafe_lock") { // A pthread mutex initialized with PTHREAD_MUTEX_INITIALIZER will have // a type of PTHREAD_MUTEX_DEFAULT, which has undefined behavior if you // try to re-lock it from the same thread when you already hold a lock // (https://pubs.opengroup.org/onlinepubs/9699919799/functions/pthread_mutex_init.html). // This is the case even if PTHREAD_MUTEX_DEFAULT == PTHREAD_MUTEX_NORMAL // (https://github.com/rust-lang/rust/issues/33770#issuecomment-220847521) -- in that // case, `pthread_mutexattr_settype(PTHREAD_MUTEX_DEFAULT)` will of course be the same // as setting it to `PTHREAD_MUTEX_NORMAL`, but not setting any mode will result in // a Mutex where re-locking is UB. // // In practice, glibc takes advantage of this undefined behavior to // implement hardware lock elision, which uses hardware transactional // memory to avoid acquiring the lock. While a transaction is in // progress, the lock appears to be unlocked. This isn't a problem for // other threads since the transactional memory will abort if a conflict // is detected, however no abort is generated when re-locking from the // same thread. // // Since locking the same mutex twice will result in two aliasing &mut // references, we instead create the mutex with type // PTHREAD_MUTEX_NORMAL which is guaranteed to deadlock if we try to // re-lock it from the same thread, thus avoiding undefined behavior. unsafe { let mut attr = MaybeUninit::::uninit(); libc::pthread_mutexattr_init(attr.as_mut_ptr()); let attr = PthreadMutexAttr(&mut attr); libc::pthread_mutexattr_settype(attr.0.as_mut_ptr(), libc::PTHREAD_MUTEX_NORMAL); libc::pthread_mutex_init(mutex.0.get(), attr.0.as_ptr()); } } mutex } } impl Deref for RawMutex { type Target = UnsafeCell; fn deref(&self) -> &Self::Target { &self.0 } } pub struct Mutex(LazyBox); unsafe impl Send for Mutex {} unsafe impl Sync for Mutex {} impl Mutex { #[inline] pub const fn new() -> Self { Self(LazyBox::new()) } /// Locks the mutex /// /// # Safety /// /// UB occurs if the mutex is already locked by the current thread and the /// `unsafe_lock` feature is enabled. #[inline] pub unsafe fn lock(&self) { // safety: the pointer is valid pthread_mutex_lock(self.0.get()); } /// If the mutex is unlocked, it is locked, and this function returns /// `true'. Otherwise, `false` is returned. #[inline] pub unsafe fn try_lock(&self) -> bool { // safety: the pointer is valid unsafe { pthread_mutex_trylock(self.0.get()) == 0 } } /// Unlocks the mutex /// /// # Safety /// /// UB occurs if the mutex is already unlocked or if it has been locked on /// a different thread. #[inline] pub unsafe fn unlock(&self) { // safety: the pointer is valid pthread_mutex_unlock(self.0.get()); } pub unsafe fn is_locked(&self) -> bool { if self.try_lock() { unsafe { self.unlock(); } false } else { true } } }