summaryrefslogtreecommitdiff
path: root/src/pthread.rs
diff options
context:
space:
mode:
authorMica White <botahamec@outlook.com>2025-12-08 20:14:03 -0500
committerMica White <botahamec@outlook.com>2025-12-08 20:14:03 -0500
commitc31f4ce84c3c8b3f89a05890df775d4e766aaadb (patch)
tree40169c1240717002197c85985f9bb652dd4b0af8 /src/pthread.rs
First commitHEADmain
Diffstat (limited to 'src/pthread.rs')
-rwxr-xr-xsrc/pthread.rs118
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
+ }
+ }
+}