diff options
Diffstat (limited to 'library/std/src/sys/sync/condvar/pthread.rs')
| -rw-r--r-- | library/std/src/sys/sync/condvar/pthread.rs | 210 |
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 + } } } |
