diff options
| author | Mica White <botahamec@outlook.com> | 2025-12-08 20:14:03 -0500 |
|---|---|---|
| committer | Mica White <botahamec@outlook.com> | 2025-12-08 20:14:03 -0500 |
| commit | c31f4ce84c3c8b3f89a05890df775d4e766aaadb (patch) | |
| tree | 40169c1240717002197c85985f9bb652dd4b0af8 /src/pthread.rs | |
Diffstat (limited to 'src/pthread.rs')
| -rwxr-xr-x | src/pthread.rs | 118 |
1 files changed, 118 insertions, 0 deletions
diff --git a/src/pthread.rs b/src/pthread.rs new file mode 100755 index 0000000..b549e1d --- /dev/null +++ b/src/pthread.rs @@ -0,0 +1,118 @@ +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 + } + } +} |
