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<pthread_mutex_t>);
impl LazyInit for RawMutex {
fn init() -> Box<Self> {
// 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::<libc::pthread_mutexattr_t>::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<pthread_mutex_t>;
fn deref(&self) -> &Self::Target {
&self.0
}
}
pub struct Mutex(LazyBox<RawMutex>);
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
}
}
}
|