diff --git a/library/backtrace b/library/backtrace index 230570f2dac80..72265bea21089 160000 --- a/library/backtrace +++ b/library/backtrace @@ -1 +1 @@ -Subproject commit 230570f2dac80a601f5c0b30da00cc9480bd35eb +Subproject commit 72265bea210891ae47bbe6d4f17b493ef0606619 diff --git a/library/std/src/sys/pal/hermit/thread.rs b/library/std/src/sys/pal/hermit/thread.rs index 41f2c3e212355..fb0c9bcee3371 100644 --- a/library/std/src/sys/pal/hermit/thread.rs +++ b/library/std/src/sys/pal/hermit/thread.rs @@ -86,6 +86,14 @@ impl Thread { } } + pub fn sleep_until(deadline: Instant) { + let now = Instant::now(); + + if let Some(delay) = deadline.checked_duration_since(now) { + sleep(delay); + } + } + pub fn join(self) { unsafe { let _ = hermit_abi::join(self.tid); diff --git a/library/std/src/sys/pal/itron/thread.rs b/library/std/src/sys/pal/itron/thread.rs index 04095e1a7cf99..5437188e9e046 100644 --- a/library/std/src/sys/pal/itron/thread.rs +++ b/library/std/src/sys/pal/itron/thread.rs @@ -205,6 +205,14 @@ impl Thread { } } + pub fn sleep_until(deadline: Instant) { + let now = Instant::now(); + + if let Some(delay) = deadline.checked_duration_since(now) { + sleep(delay); + } + } + pub fn join(self) { // Safety: `ThreadInner` is alive at this point let inner = unsafe { self.p_inner.as_ref() }; diff --git a/library/std/src/sys/pal/sgx/thread.rs b/library/std/src/sys/pal/sgx/thread.rs index cecd53c352c5a..0e68899b0b33f 100644 --- a/library/std/src/sys/pal/sgx/thread.rs +++ b/library/std/src/sys/pal/sgx/thread.rs @@ -131,6 +131,14 @@ impl Thread { usercalls::wait_timeout(0, dur, || true); } + pub fn sleep_until(deadline: Instant) { + let now = Instant::now(); + + if let Some(delay) = deadline.checked_duration_since(now) { + sleep(delay); + } + } + pub fn join(self) { self.0.wait(); } diff --git a/library/std/src/sys/pal/unix/thread.rs b/library/std/src/sys/pal/unix/thread.rs index 040246618360f..de7845b6e873b 100644 --- a/library/std/src/sys/pal/unix/thread.rs +++ b/library/std/src/sys/pal/unix/thread.rs @@ -303,6 +303,93 @@ impl Thread { } } + #[cfg(not(any( + target_os = "freebsd", + target_os = "netbsd", + target_os = "linux", + target_os = "android", + target_os = "solaris", + target_os = "illumos", + target_os = "dragonfly", + target_os = "hurd", + target_os = "fuchsia", + target_os = "vxworks", + target_vendor = "apple" + )))] + pub fn sleep_until(deadline: Instant) { + let now = Instant::now(); + + if let Some(delay) = deadline.checked_duration_since(now) { + sleep(delay); + } + } + + // Note depends on clock_nanosleep (not supported on os's by apple) + #[cfg(any( + target_os = "freebsd", + target_os = "netbsd", + target_os = "linux", + target_os = "android", + target_os = "solaris", + target_os = "illumos", + target_os = "dragonfly", + target_os = "hurd", + target_os = "fuchsia", + target_os = "vxworks", + ))] + pub fn sleep_until(deadline: crate::time::Instant) { + let mut ts = deadline + .into_inner() + .into_timespec() + .to_timespec() + .expect("Timespec is narrower then libc::timespec thus conversion can't fail"); + let ts_ptr = &mut ts as *mut _; + + // If we're awoken with a signal and the return value is -1 + // clock_nanosleep needs to be called again. + unsafe { + while libc::clock_nanosleep(libc::CLOCK_MONOTONIC, libc::TIMER_ABSTIME, ts_ptr, ts_ptr) + == -1 + { + assert_eq!( + os::errno(), + libc::EINTR, + "clock nanosleep should only return an error if interrupted" + ); + } + } + } + + #[cfg(target_vendor = "apple")] + pub fn sleep_until(deadline: crate::time::Instant) { + use core::mem::MaybeUninit; + + use super::time::Timespec; + + let Timespec { tv_sec, tv_nsec } = deadline.into_inner().into_timespec(); + let nanos = (tv_sec as u64).saturating_mul(1_000_000_000).saturating_add(tv_nsec.0 as u64); + + let mut info = MaybeUninit::uninit(); + unsafe { + let ret = mach_timebase_info(info.as_mut_ptr()); + assert_eq!(ret, KERN_SUCCESS); + + let info = info.assume_init(); + let ticks = nanos * (info.denom as u64) / (info.numer as u64); + + loop { + // There are no docs on the mach_wait_until some details can be + // learned from the XNU source code: + // https://github.com/apple-oss-distributions/xnu/blob/94d3b452840153a99b38a3a9659680b2a006908e/osfmk/kern/clock.c#L1507-L1543 + let ret = mach_wait_until(ticks); + if ret == KERN_SUCCESS { + break; + } + assert_eq!(ret, KERN_ABORTED); + } + } + } + pub fn join(self) { let id = self.into_id(); let ret = unsafe { libc::pthread_join(id, ptr::null_mut()) }; @@ -318,6 +405,27 @@ impl Thread { } } +// See https://github.com/apple-oss-distributions/xnu/blob/94d3b452840153a99b38a3a9659680b2a006908e/osfmk/mach/kern_return.h +#[cfg(target_vendor = "apple")] +const KERN_SUCCESS: libc::c_int = 0; +#[cfg(target_vendor = "apple")] +const KERN_ABORTED: libc::c_int = 14; + +// See https://github.com/apple-oss-distributions/xnu/blob/94d3b452840153a99b38a3a9659680b2a006908e/osfmk/mach/mach_time.h +#[cfg(target_vendor = "apple")] +#[repr(C)] +struct mach_timebase_info_type { + numer: u32, + denom: u32, +} + +#[cfg(target_vendor = "apple")] +extern "C" { + fn mach_wait_until(deadline: u64) -> libc::c_int; + fn mach_timebase_info(info: *mut mach_timebase_info_type) -> libc::c_int; + +} + impl Drop for Thread { fn drop(&mut self) { let ret = unsafe { libc::pthread_detach(self.id) }; diff --git a/library/std/src/sys/pal/unix/time.rs b/library/std/src/sys/pal/unix/time.rs index 535fe6b27d91e..83c6b21975070 100644 --- a/library/std/src/sys/pal/unix/time.rs +++ b/library/std/src/sys/pal/unix/time.rs @@ -19,7 +19,7 @@ pub(in crate::sys) const TIMESPEC_MAX_CAPPED: libc::timespec = libc::timespec { #[repr(transparent)] #[rustc_layout_scalar_valid_range_start(0)] #[rustc_layout_scalar_valid_range_end(999_999_999)] -struct Nanoseconds(u32); +pub(crate) struct Nanoseconds(pub(crate) u32); #[derive(Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)] pub struct SystemTime { @@ -28,8 +28,8 @@ pub struct SystemTime { #[derive(Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)] pub(crate) struct Timespec { - tv_sec: i64, - tv_nsec: Nanoseconds, + pub(crate) tv_sec: i64, + pub(crate) tv_nsec: Nanoseconds, } impl SystemTime { @@ -287,6 +287,10 @@ impl Instant { pub fn checked_sub_duration(&self, other: &Duration) -> Option { Some(Instant { t: self.t.checked_sub_duration(other)? }) } + + pub(crate) fn into_timespec(self) -> Timespec { + self.t + } } impl fmt::Debug for Instant { diff --git a/library/std/src/sys/pal/wasi/thread.rs b/library/std/src/sys/pal/wasi/thread.rs index 4b83870fdea6c..54f5f45310ad1 100644 --- a/library/std/src/sys/pal/wasi/thread.rs +++ b/library/std/src/sys/pal/wasi/thread.rs @@ -3,7 +3,7 @@ use crate::ffi::CStr; use crate::num::NonZero; use crate::sys::unsupported; -use crate::time::Duration; +use crate::time::{Duration, Instant}; use crate::{io, mem}; cfg_if::cfg_if! { @@ -136,41 +136,25 @@ impl Thread { } pub fn sleep(dur: Duration) { - let mut nanos = dur.as_nanos(); + let mut nanos_all = dur.as_nanos(); while nanos > 0 { - const USERDATA: wasi::Userdata = 0x0123_45678; - - let clock = wasi::SubscriptionClock { - id: wasi::CLOCKID_MONOTONIC, - timeout: u64::try_from(nanos).unwrap_or(u64::MAX), - precision: 0, - flags: 0, - }; - nanos -= u128::from(clock.timeout); - - let in_ = wasi::Subscription { - userdata: USERDATA, - u: wasi::SubscriptionU { tag: 0, u: wasi::SubscriptionUU { clock } }, - }; - unsafe { - let mut event: wasi::Event = mem::zeroed(); - let res = wasi::poll_oneoff(&in_, &mut event, 1); - match (res, event) { - ( - Ok(1), - wasi::Event { - userdata: USERDATA, - error: wasi::ERRNO_SUCCESS, - type_: wasi::EVENTTYPE_CLOCK, - .. - }, - ) => {} - _ => panic!("thread::sleep(): unexpected result of poll_oneoff"), - } - } + let nanos_sleepable = u64::try_from(full_nanos).unwrap_or(u64::MAX); + nanos_all -= u128::from(nanos_sleepable); + sleep_with(nanos_sleepable, wasi::CLOCKID_MONOTONIC, 0); } } + pub fn sleep_until(deadline: Instant) { + let nanos = deadline.into_inner().into_inner().as_nanos(); + assert!(nanos <= u64::MAX as u128); + + sleep_with( + nanos as u64, + wasi::CLOCKID_MONOTONIC, + wasi::SUBCLOCKFLAGS_SUBSCRIPTION_CLOCK_ABSTIME, + ); + } + pub fn join(self) { cfg_if::cfg_if! { if #[cfg(target_feature = "atomics")] { @@ -186,6 +170,33 @@ impl Thread { } } +fn sleep_with(nanos: u64, clock_id: wasi::Clockid, flags: u16) { + const USERDATA: wasi::Userdata = 0x0123_45678; + + let clock = wasi::SubscriptionClock { id: clock_id, timeout: nanos, precision: 0, flags }; + + let in_ = wasi::Subscription { + userdata: USERDATA, + u: wasi::SubscriptionU { tag: 0, u: wasi::SubscriptionUU { clock } }, + }; + unsafe { + let mut event: wasi::Event = mem::zeroed(); + let res = wasi::poll_oneoff(&in_, &mut event, 1); + match (res, event) { + ( + Ok(1), + wasi::Event { + userdata: USERDATA, + error: wasi::ERRNO_SUCCESS, + type_: wasi::EVENTTYPE_CLOCK, + .. + }, + ) => {} + _ => panic!("thread::sleep(): unexpected result of poll_oneoff"), + } + } +} + pub fn available_parallelism() -> io::Result> { unsupported() } diff --git a/library/std/src/sys/pal/wasi/time.rs b/library/std/src/sys/pal/wasi/time.rs index 0d8d0b59ac14a..70dddfe4c2868 100644 --- a/library/std/src/sys/pal/wasi/time.rs +++ b/library/std/src/sys/pal/wasi/time.rs @@ -36,6 +36,10 @@ impl Instant { pub fn checked_sub_duration(&self, other: &Duration) -> Option { Some(Instant(self.0.checked_sub(*other)?)) } + + pub(crate) fn into_inner(self) -> Duration { + self.0 + } } impl SystemTime { diff --git a/library/std/src/sys/pal/windows/thread.rs b/library/std/src/sys/pal/windows/thread.rs index 2c8ce42f4148b..ebf1251f05dc7 100644 --- a/library/std/src/sys/pal/windows/thread.rs +++ b/library/std/src/sys/pal/windows/thread.rs @@ -8,7 +8,7 @@ use crate::os::windows::io::{AsRawHandle, HandleOrNull}; use crate::sys::handle::Handle; use crate::sys::{c, stack_overflow}; use crate::sys_common::FromInner; -use crate::time::Duration; +use crate::time::{Duration, Instant}; use crate::{io, ptr}; pub const DEFAULT_MIN_STACK_SIZE: usize = 2 * 1024 * 1024; @@ -105,6 +105,14 @@ impl Thread { } } + pub fn sleep_until(deadline: Instant) { + let now = Instant::now(); + + if let Some(delay) = deadline.checked_duration_since(now) { + Self::sleep(delay); + } + } + pub fn handle(&self) -> &Handle { &self.handle } diff --git a/library/std/src/sys/pal/xous/thread.rs b/library/std/src/sys/pal/xous/thread.rs index 0ebb46dc19faa..edc64f5e4d4a9 100644 --- a/library/std/src/sys/pal/xous/thread.rs +++ b/library/std/src/sys/pal/xous/thread.rs @@ -128,6 +128,14 @@ impl Thread { } } + pub fn sleep_until(deadline: Instant) { + let now = Instant::now(); + + if let Some(delay) = deadline.checked_duration_since(now) { + sleep(delay); + } + } + pub fn join(self) { join_thread(self.tid).unwrap(); } diff --git a/library/std/src/thread/mod.rs b/library/std/src/thread/mod.rs index 227ee9d64f375..5902b44ae7c72 100644 --- a/library/std/src/thread/mod.rs +++ b/library/std/src/thread/mod.rs @@ -878,8 +878,36 @@ pub fn sleep(dur: Duration) { /// /// # Platform-specific behavior /// -/// This function uses [`sleep`] internally, see its platform-specific behavior. +/// In most cases this function will call an OS specific function. Where that +/// is not supported [`sleep`] is used. Those platforms are referred to as other +/// in the table below. /// +/// # Underlying System calls +/// +/// The following system calls are [currently] being used: +/// +/// +/// | Platform | System call | +/// |-----------|----------------------------------------------------------------------| +/// | Linux | [clock_nanosleep] (Monotonic clock) | +/// | BSD except OpenBSD | [clock_nanosleep] (Monotonic Clock)] | +/// | Android | [clock_nanosleep] (Monotonic Clock)] | +/// | Solaris | [clock_nanosleep] (Monotonic Clock)] | +/// | Illumos | [clock_nanosleep] (Monotonic Clock)] | +/// | Dragonfly | [clock_nanosleep] (Monotonic Clock)] | +/// | Hurd | [clock_nanosleep] (Monotonic Clock)] | +/// | Fuchsia | [clock_nanosleep] (Monotonic Clock)] | +/// | Vxworks | [clock_nanosleep] (Monotonic Clock)] | +/// | Darwin | [mach_wait_until] | +/// | WASI | [subscription_clock] | +/// | Other | `sleep_until` uses [`sleep`] and does not issue a syscall itself | +/// +/// [currently]: crate::io#platform-specific-behavior +/// [clock_nanosleep]: https://linux.die.net/man/3/clock_nanosleep +/// [subscription_clock]: https://github.com/WebAssembly/WASI/blob/main/legacy/preview1/docs.md#-subscription_clock-record +/// [mach_wait_until]: https://developer.apple.com/library/archive/technotes/tn2169/_index.html +/// +/// **Disclaimer:** These system calls might change over time. /// /// # Examples /// @@ -904,9 +932,9 @@ pub fn sleep(dur: Duration) { /// } /// ``` /// -/// A slow api we must not call too fast and which takes a few +/// A slow API we must not call too fast and which takes a few /// tries before succeeding. By using `sleep_until` the time the -/// api call takes does not influence when we retry or when we give up +/// API call takes does not influence when we retry or when we give up /// /// ```no_run /// #![feature(thread_sleep_until)] @@ -941,11 +969,7 @@ pub fn sleep(dur: Duration) { /// ``` #[unstable(feature = "thread_sleep_until", issue = "113752")] pub fn sleep_until(deadline: Instant) { - let now = Instant::now(); - - if let Some(delay) = deadline.checked_duration_since(now) { - sleep(delay); - } + imp::Thread::sleep_until(deadline) } /// Used to ensure that `park` and `park_timeout` do not unwind, as that can diff --git a/library/std/src/time.rs b/library/std/src/time.rs index 9f4f8a0d0880c..6befff240534c 100644 --- a/library/std/src/time.rs +++ b/library/std/src/time.rs @@ -403,6 +403,12 @@ impl Instant { pub fn checked_sub(&self, duration: Duration) -> Option { self.0.checked_sub_duration(&duration).map(Instant) } + + // used by platform specific sleep_until implementations, no every platform has one + #[allow(unused)] + pub(crate) fn into_inner(self) -> time::Instant { + self.0 + } } #[stable(feature = "time2", since = "1.8.0")] diff --git a/library/std/tests/thread.rs b/library/std/tests/thread.rs index 1bb17d149fa10..db9e306e0be63 100644 --- a/library/std/tests/thread.rs +++ b/library/std/tests/thread.rs @@ -1,7 +1,8 @@ +#![feature(thread_sleep_until)] use std::cell::{Cell, RefCell}; use std::sync::{Arc, Mutex}; use std::thread; -use std::time::Duration; +use std::time::{Duration, Instant}; #[test] #[cfg_attr(any(target_os = "emscripten", target_os = "wasi"), ignore)] // no threads @@ -78,3 +79,14 @@ fn available_parallelism() { // check that std::thread::available_parallelism() returns a valid value assert!(thread::available_parallelism().is_ok()); } + +#[test] +fn sleep_until() { + let now = Instant::now(); + let period = Duration::from_millis(100); + let deadline = now + period; + thread::sleep_until(deadline); + + let elapsed = now.elapsed(); + assert!(elapsed >= period); +} diff --git a/library/stdarch b/library/stdarch index c881fe3231b39..df3618d9f3516 160000 --- a/library/stdarch +++ b/library/stdarch @@ -1 +1 @@ -Subproject commit c881fe3231b3947a4766aa15a26a93022fbb8723 +Subproject commit df3618d9f35165f4bc548114e511c49c29e1fd9b