about summary refs log tree commit diff
path: root/library/std/src/sys/sync/condvar/pthread.rs
diff options
context:
space:
mode:
Diffstat (limited to 'library/std/src/sys/sync/condvar/pthread.rs')
-rw-r--r--library/std/src/sys/sync/condvar/pthread.rs210
1 files changed, 51 insertions, 159 deletions
diff --git a/library/std/src/sys/sync/condvar/pthread.rs b/library/std/src/sys/sync/condvar/pthread.rs
index cee728e35cd..4d2f9c0aaba 100644
--- a/library/std/src/sys/sync/condvar/pthread.rs
+++ b/library/std/src/sys/sync/condvar/pthread.rs
@@ -1,196 +1,88 @@
-use crate::cell::UnsafeCell;
+#![forbid(unsafe_op_in_unsafe_fn)]
+
+use crate::pin::Pin;
 use crate::ptr;
-use crate::sync::atomic::AtomicPtr;
+use crate::sync::atomic::AtomicUsize;
 use crate::sync::atomic::Ordering::Relaxed;
+use crate::sys::pal::sync as pal;
 use crate::sys::sync::{Mutex, OnceBox};
-#[cfg(not(target_os = "nto"))]
-use crate::sys::time::TIMESPEC_MAX;
-#[cfg(target_os = "nto")]
-use crate::sys::time::TIMESPEC_MAX_CAPPED;
-use crate::time::Duration;
-
-struct AllocatedCondvar(UnsafeCell<libc::pthread_cond_t>);
+use crate::time::{Duration, Instant};
 
 pub struct Condvar {
-    inner: OnceBox<AllocatedCondvar>,
-    mutex: AtomicPtr<libc::pthread_mutex_t>,
-}
-
-unsafe impl Send for AllocatedCondvar {}
-unsafe impl Sync for AllocatedCondvar {}
-
-impl AllocatedCondvar {
-    fn new() -> Box<Self> {
-        let condvar = Box::new(AllocatedCondvar(UnsafeCell::new(libc::PTHREAD_COND_INITIALIZER)));
-
-        cfg_if::cfg_if! {
-            if #[cfg(any(
-                target_os = "l4re",
-                target_os = "android",
-                target_os = "redox",
-                target_vendor = "apple",
-            ))] {
-                // `pthread_condattr_setclock` is unfortunately not supported on these platforms.
-            } else if #[cfg(any(target_os = "espidf", target_os = "horizon", target_os = "teeos"))] {
-                // NOTE: ESP-IDF's PTHREAD_COND_INITIALIZER support is not released yet
-                // So on that platform, init() should always be called
-                // Moreover, that platform does not have pthread_condattr_setclock support,
-                // hence that initialization should be skipped as well
-                //
-                // Similar story for the 3DS (horizon).
-                let r = unsafe { libc::pthread_cond_init(condvar.0.get(), crate::ptr::null()) };
-                assert_eq!(r, 0);
-            } else {
-                use crate::mem::MaybeUninit;
-                let mut attr = MaybeUninit::<libc::pthread_condattr_t>::uninit();
-                let r = unsafe { libc::pthread_condattr_init(attr.as_mut_ptr()) };
-                assert_eq!(r, 0);
-                let r = unsafe { libc::pthread_condattr_setclock(attr.as_mut_ptr(), libc::CLOCK_MONOTONIC) };
-                assert_eq!(r, 0);
-                let r = unsafe { libc::pthread_cond_init(condvar.0.get(), attr.as_ptr()) };
-                assert_eq!(r, 0);
-                let r = unsafe { libc::pthread_condattr_destroy(attr.as_mut_ptr()) };
-                assert_eq!(r, 0);
-            }
-        }
-
-        condvar
-    }
-}
-
-impl Drop for AllocatedCondvar {
-    #[inline]
-    fn drop(&mut self) {
-        let r = unsafe { libc::pthread_cond_destroy(self.0.get()) };
-        if cfg!(target_os = "dragonfly") {
-            // On DragonFly pthread_cond_destroy() returns EINVAL if called on
-            // a condvar that was just initialized with
-            // libc::PTHREAD_COND_INITIALIZER. Once it is used or
-            // pthread_cond_init() is called, this behavior no longer occurs.
-            debug_assert!(r == 0 || r == libc::EINVAL);
-        } else {
-            debug_assert_eq!(r, 0);
-        }
-    }
+    cvar: OnceBox<pal::Condvar>,
+    mutex: AtomicUsize,
 }
 
 impl Condvar {
     pub const fn new() -> Condvar {
-        Condvar { inner: OnceBox::new(), mutex: AtomicPtr::new(ptr::null_mut()) }
+        Condvar { cvar: OnceBox::new(), mutex: AtomicUsize::new(0) }
     }
 
-    fn get(&self) -> *mut libc::pthread_cond_t {
-        self.inner.get_or_init(AllocatedCondvar::new).0.get()
+    #[inline]
+    fn get(&self) -> Pin<&pal::Condvar> {
+        self.cvar.get_or_init(|| {
+            let mut cvar = Box::pin(pal::Condvar::new());
+            // SAFETY: we only call `init` once, namely here.
+            unsafe { cvar.as_mut().init() };
+            cvar
+        })
     }
 
     #[inline]
-    fn verify(&self, mutex: *mut libc::pthread_mutex_t) {
-        // Relaxed is okay here because we never read through `self.addr`, and only use it to
+    fn verify(&self, mutex: Pin<&pal::Mutex>) {
+        let addr = ptr::from_ref::<pal::Mutex>(&mutex).addr();
+        // Relaxed is okay here because we never read through `self.mutex`, and only use it to
         // compare addresses.
-        match self.mutex.compare_exchange(ptr::null_mut(), mutex, Relaxed, Relaxed) {
-            Ok(_) => {}                // Stored the address
-            Err(n) if n == mutex => {} // Lost a race to store the same address
+        match self.mutex.compare_exchange(0, addr, Relaxed, Relaxed) {
+            Ok(_) => {}               // Stored the address
+            Err(n) if n == addr => {} // Lost a race to store the same address
             _ => panic!("attempted to use a condition variable with two mutexes"),
         }
     }
 
     #[inline]
     pub fn notify_one(&self) {
-        let r = unsafe { libc::pthread_cond_signal(self.get()) };
-        debug_assert_eq!(r, 0);
+        // SAFETY: we called `init` above.
+        unsafe { self.get().notify_one() }
     }
 
     #[inline]
     pub fn notify_all(&self) {
-        let r = unsafe { libc::pthread_cond_broadcast(self.get()) };
-        debug_assert_eq!(r, 0);
+        // SAFETY: we called `init` above.
+        unsafe { self.get().notify_all() }
     }
 
     #[inline]
     pub unsafe fn wait(&self, mutex: &Mutex) {
-        let mutex = mutex.get_assert_locked();
+        // SAFETY: the caller guarantees that the lock is owned, thus the mutex
+        // must have been initialized already.
+        let mutex = unsafe { mutex.pal.get_unchecked() };
         self.verify(mutex);
-        let r = libc::pthread_cond_wait(self.get(), mutex);
-        debug_assert_eq!(r, 0);
+        // SAFETY: we called `init` above, we verified that this condition
+        // variable is only used with `mutex` and the caller guarantees that
+        // `mutex` is locked by the current thread.
+        unsafe { self.get().wait(mutex) }
     }
 
-    // This implementation is used on systems that support pthread_condattr_setclock
-    // where we configure condition variable to use monotonic clock (instead of
-    // default system clock). This approach avoids all problems that result
-    // from changes made to the system time.
-    #[cfg(not(any(
-        target_os = "android",
-        target_os = "espidf",
-        target_os = "horizon",
-        target_vendor = "apple",
-    )))]
     pub unsafe fn wait_timeout(&self, mutex: &Mutex, dur: Duration) -> bool {
-        use crate::sys::time::Timespec;
-
-        let mutex = mutex.get_assert_locked();
+        // SAFETY: the caller guarantees that the lock is owned, thus the mutex
+        // must have been initialized already.
+        let mutex = unsafe { mutex.pal.get_unchecked() };
         self.verify(mutex);
 
-        #[cfg(not(target_os = "nto"))]
-        let timeout = Timespec::now(libc::CLOCK_MONOTONIC)
-            .checked_add_duration(&dur)
-            .and_then(|t| t.to_timespec())
-            .unwrap_or(TIMESPEC_MAX);
-
-        #[cfg(target_os = "nto")]
-        let timeout = Timespec::now(libc::CLOCK_MONOTONIC)
-            .checked_add_duration(&dur)
-            .and_then(|t| t.to_timespec_capped())
-            .unwrap_or(TIMESPEC_MAX_CAPPED);
-
-        let r = libc::pthread_cond_timedwait(self.get(), mutex, &timeout);
-        assert!(r == libc::ETIMEDOUT || r == 0);
-        r == 0
-    }
-
-    // This implementation is modeled after libcxx's condition_variable
-    // https://github.com/llvm-mirror/libcxx/blob/release_35/src/condition_variable.cpp#L46
-    // https://github.com/llvm-mirror/libcxx/blob/release_35/include/__mutex_base#L367
-    #[cfg(any(
-        target_os = "android",
-        target_os = "espidf",
-        target_os = "horizon",
-        target_vendor = "apple",
-    ))]
-    pub unsafe fn wait_timeout(&self, mutex: &Mutex, dur: Duration) -> bool {
-        use crate::sys::time::SystemTime;
-        use crate::time::Instant;
-
-        let mutex = mutex.get_assert_locked();
-        self.verify(mutex);
-
-        // OSX implementation of `pthread_cond_timedwait` is buggy
-        // with super long durations. When duration is greater than
-        // 0x100_0000_0000_0000 seconds, `pthread_cond_timedwait`
-        // in macOS Sierra returns error 316.
-        //
-        // This program demonstrates the issue:
-        // https://gist.github.com/stepancheg/198db4623a20aad2ad7cddb8fda4a63c
-        //
-        // To work around this issue, and possible bugs of other OSes, timeout
-        // is clamped to 1000 years, which is allowable per the API of `wait_timeout`
-        // because of spurious wakeups.
-        let dur = Duration::min(dur, Duration::from_secs(1000 * 365 * 86400));
-
-        // pthread_cond_timedwait uses system time, but we want to report timeout
-        // based on stable time.
-        let now = Instant::now();
-
-        let timeout = SystemTime::now()
-            .t
-            .checked_add_duration(&dur)
-            .and_then(|t| t.to_timespec())
-            .unwrap_or(TIMESPEC_MAX);
-
-        let r = libc::pthread_cond_timedwait(self.get(), mutex, &timeout);
-        debug_assert!(r == libc::ETIMEDOUT || r == 0);
-
-        // ETIMEDOUT is not a totally reliable method of determining timeout due
-        // to clock shifts, so do the check ourselves
-        now.elapsed() < dur
+        if pal::Condvar::PRECISE_TIMEOUT {
+            // SAFETY: we called `init` above, we verified that this condition
+            // variable is only used with `mutex` and the caller guarantees that
+            // `mutex` is locked by the current thread.
+            unsafe { self.get().wait_timeout(mutex, dur) }
+        } else {
+            // Timeout reports are not reliable, so do the check ourselves.
+            let now = Instant::now();
+            // SAFETY: we called `init` above, we verified that this condition
+            // variable is only used with `mutex` and the caller guarantees that
+            // `mutex` is locked by the current thread.
+            let woken = unsafe { self.get().wait_timeout(mutex, dur) };
+            woken || now.elapsed() < dur
+        }
     }
 }