From 4238e5e2c68c1c01c1db651e75c9a293e86f98f9 Mon Sep 17 00:00:00 2001 From: dvdsk Date: Sat, 31 May 2025 15:33:46 +0200 Subject: [PATCH 01/16] sleep_until: add platform specific implementations Except for unix they are all copies of the existing non platform specific implementation. This is the only way since tidy does not allow `#[cfg(target_os = ` in src/thread/mod.rs. Once this is merged more specializations will follow. --- library/std/src/sys/pal/hermit/thread.rs | 10 +++- library/std/src/sys/pal/itron/thread.rs | 10 +++- library/std/src/sys/pal/sgx/thread.rs | 10 +++- library/std/src/sys/pal/teeos/thread.rs | 10 +++- library/std/src/sys/pal/uefi/thread.rs | 10 +++- library/std/src/sys/pal/unix/thread.rs | 59 ++++++++++++++++++- library/std/src/sys/pal/unix/time.rs | 7 +++ library/std/src/sys/pal/unsupported/thread.rs | 4 ++ library/std/src/sys/pal/wasi/thread.rs | 10 +++- .../std/src/sys/pal/wasm/atomics/thread.rs | 10 +++- library/std/src/sys/pal/windows/thread.rs | 10 +++- library/std/src/sys/pal/xous/thread.rs | 10 +++- library/std/src/thread/mod.rs | 39 +++++++++--- library/std/src/time.rs | 7 +++ library/std/tests/thread.rs | 14 ++++- 15 files changed, 200 insertions(+), 20 deletions(-) diff --git a/library/std/src/sys/pal/hermit/thread.rs b/library/std/src/sys/pal/hermit/thread.rs index bb68a824fc313..9bc5a16b80023 100644 --- a/library/std/src/sys/pal/hermit/thread.rs +++ b/library/std/src/sys/pal/hermit/thread.rs @@ -4,7 +4,7 @@ use super::hermit_abi; use crate::ffi::CStr; use crate::mem::ManuallyDrop; use crate::num::NonZero; -use crate::time::Duration; +use crate::time::{Duration, Instant}; use crate::{io, ptr}; pub type Tid = hermit_abi::Tid; @@ -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) { + Self::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 a974f4f17ae67..813e1cbcd58fb 100644 --- a/library/std/src/sys/pal/itron/thread.rs +++ b/library/std/src/sys/pal/itron/thread.rs @@ -10,7 +10,7 @@ use crate::mem::ManuallyDrop; use crate::num::NonZero; use crate::ptr::NonNull; use crate::sync::atomic::{Atomic, AtomicUsize, Ordering}; -use crate::time::Duration; +use crate::time::{Duration, Instant}; use crate::{hint, io}; pub struct Thread { @@ -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) { + Self::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 219ef1b7a9897..85f6dcd96b4a5 100644 --- a/library/std/src/sys/pal/sgx/thread.rs +++ b/library/std/src/sys/pal/sgx/thread.rs @@ -5,7 +5,7 @@ use super::unsupported; use crate::ffi::CStr; use crate::io; use crate::num::NonZero; -use crate::time::Duration; +use crate::time::{Duration, Instant}; pub struct Thread(task_queue::JoinHandle); @@ -132,6 +132,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) { + Self::sleep(delay); + } + } + pub fn join(self) { self.0.wait(); } diff --git a/library/std/src/sys/pal/teeos/thread.rs b/library/std/src/sys/pal/teeos/thread.rs index e3b4908f85863..b9cdc7a2a58bb 100644 --- a/library/std/src/sys/pal/teeos/thread.rs +++ b/library/std/src/sys/pal/teeos/thread.rs @@ -2,7 +2,7 @@ use crate::ffi::CStr; use crate::mem::{self, ManuallyDrop}; use crate::num::NonZero; use crate::sys::os; -use crate::time::Duration; +use crate::time::{Duration, Instant}; use crate::{cmp, io, ptr}; pub const DEFAULT_MIN_STACK_SIZE: usize = 8 * 1024; @@ -109,6 +109,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); + } + } + /// must join, because no pthread_detach supported pub fn join(self) { let id = self.into_id(); diff --git a/library/std/src/sys/pal/uefi/thread.rs b/library/std/src/sys/pal/uefi/thread.rs index 7d4006ff4b2f7..e4776ec42fbba 100644 --- a/library/std/src/sys/pal/uefi/thread.rs +++ b/library/std/src/sys/pal/uefi/thread.rs @@ -3,7 +3,7 @@ use crate::ffi::CStr; use crate::io; use crate::num::NonZero; use crate::ptr::NonNull; -use crate::time::Duration; +use crate::time::{Duration, Instant}; pub struct Thread(!); @@ -39,6 +39,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 join(self) { self.0 } diff --git a/library/std/src/sys/pal/unix/thread.rs b/library/std/src/sys/pal/unix/thread.rs index d8b189413f4a3..7ee7241f1000b 100644 --- a/library/std/src/sys/pal/unix/thread.rs +++ b/library/std/src/sys/pal/unix/thread.rs @@ -6,7 +6,7 @@ use crate::sys::weak::dlsym; #[cfg(any(target_os = "solaris", target_os = "illumos", target_os = "nto",))] use crate::sys::weak::weak; use crate::sys::{os, stack_overflow}; -use crate::time::Duration; +use crate::time::{Duration, Instant}; use crate::{cmp, io, ptr}; #[cfg(not(any( target_os = "l4re", @@ -296,6 +296,63 @@ impl Thread { } } + // Any unix that has clock_nanosleep + #[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: 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" + ); + } + } + } + + // Any unix that does not have clock_nanosleep + #[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", + )))] + pub fn sleep_until(deadline: Instant) { + let now = Instant::now(); + + if let Some(delay) = deadline.checked_duration_since(now) { + Self::sleep(delay); + } + } + pub fn join(self) { let id = self.into_id(); let ret = unsafe { libc::pthread_join(id, ptr::null_mut()) }; diff --git a/library/std/src/sys/pal/unix/time.rs b/library/std/src/sys/pal/unix/time.rs index 0074d7674741b..d58e1c8b564f5 100644 --- a/library/std/src/sys/pal/unix/time.rs +++ b/library/std/src/sys/pal/unix/time.rs @@ -291,6 +291,13 @@ impl Instant { pub fn checked_sub_duration(&self, other: &Duration) -> Option { Some(Instant { t: self.t.checked_sub_duration(other)? }) } + + // reason for allow(unused): this is needed by the `sleep_until` implementation + // for some unix platforms not all. + #[allow(unused)] + pub(crate) fn into_timespec(self) -> Timespec { + self.t + } } impl fmt::Debug for Instant { diff --git a/library/std/src/sys/pal/unsupported/thread.rs b/library/std/src/sys/pal/unsupported/thread.rs index 89f8bad7026ee..9e52d5c94bc68 100644 --- a/library/std/src/sys/pal/unsupported/thread.rs +++ b/library/std/src/sys/pal/unsupported/thread.rs @@ -26,6 +26,10 @@ impl Thread { panic!("can't sleep"); } + pub fn sleep_until(_deadline: Instant) { + panic!("can't sleep"); + } + pub fn join(self) { self.0 } diff --git a/library/std/src/sys/pal/wasi/thread.rs b/library/std/src/sys/pal/wasi/thread.rs index cc569bb3daf68..5f21a553673a3 100644 --- a/library/std/src/sys/pal/wasi/thread.rs +++ b/library/std/src/sys/pal/wasi/thread.rs @@ -2,7 +2,7 @@ use crate::ffi::CStr; use crate::num::NonZero; -use crate::time::Duration; +use crate::time::{Duration, Instant}; use crate::{io, mem}; cfg_if::cfg_if! { @@ -171,6 +171,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 join(self) { cfg_if::cfg_if! { if #[cfg(target_feature = "atomics")] { diff --git a/library/std/src/sys/pal/wasm/atomics/thread.rs b/library/std/src/sys/pal/wasm/atomics/thread.rs index dd5aff391fd8b..44ce3eab109f4 100644 --- a/library/std/src/sys/pal/wasm/atomics/thread.rs +++ b/library/std/src/sys/pal/wasm/atomics/thread.rs @@ -2,7 +2,7 @@ use crate::ffi::CStr; use crate::io; use crate::num::NonZero; use crate::sys::unsupported; -use crate::time::Duration; +use crate::time::{Duration, Instant}; pub struct Thread(!); @@ -41,6 +41,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 join(self) {} } diff --git a/library/std/src/sys/pal/windows/thread.rs b/library/std/src/sys/pal/windows/thread.rs index 45e52cf4d047f..147851717553a 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; @@ -106,6 +106,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..1b344e984dc36 100644 --- a/library/std/src/sys/pal/xous/thread.rs +++ b/library/std/src/sys/pal/xous/thread.rs @@ -8,7 +8,7 @@ use crate::os::xous::ffi::{ map_memory, update_memory_flags, }; use crate::os::xous::services::{TicktimerScalar, ticktimer_server}; -use crate::time::Duration; +use crate::time::{Duration, Instant}; pub struct Thread { tid: ThreadId, @@ -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) { + Self::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 26b2fb4472436..7ee6990be8bb7 100644 --- a/library/std/src/thread/mod.rs +++ b/library/std/src/thread/mod.rs @@ -897,8 +897,33 @@ 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)] | +/// | 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 /// @@ -923,9 +948,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)] @@ -960,11 +985,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 03af35e809c91..08d137dc14ac2 100644 --- a/library/std/src/time.rs +++ b/library/std/src/time.rs @@ -407,6 +407,13 @@ 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. + // reason for #[allow(unused)]: not every platform has a specific `sleep_until`. + #[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..32561dd6ab6a3 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 @@ -17,6 +18,17 @@ fn sleep_very_long() { assert_eq!(*finished.lock().unwrap(), false); } +#[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); +} + #[test] fn thread_local_containing_const_statements() { // This exercises the `const $init:block` cases of the thread_local macro. From 395aaf8302b822127dfcb77e02fe25880ef60d21 Mon Sep 17 00:00:00 2001 From: David Kleingeld Date: Tue, 3 Jun 2025 08:13:26 +0200 Subject: [PATCH 02/16] sleep_until: add reason to allow(unused) Co-authored-by: Josh Stone --- library/std/src/sys/pal/unix/time.rs | 4 +--- library/std/src/time.rs | 3 +-- 2 files changed, 2 insertions(+), 5 deletions(-) diff --git a/library/std/src/sys/pal/unix/time.rs b/library/std/src/sys/pal/unix/time.rs index d58e1c8b564f5..afce740af9f80 100644 --- a/library/std/src/sys/pal/unix/time.rs +++ b/library/std/src/sys/pal/unix/time.rs @@ -292,9 +292,7 @@ impl Instant { Some(Instant { t: self.t.checked_sub_duration(other)? }) } - // reason for allow(unused): this is needed by the `sleep_until` implementation - // for some unix platforms not all. - #[allow(unused)] + #[allow(unused, reason = "needed by the `sleep_until` on some unix platforms")] pub(crate) fn into_timespec(self) -> Timespec { self.t } diff --git a/library/std/src/time.rs b/library/std/src/time.rs index 08d137dc14ac2..b4d62b5688a22 100644 --- a/library/std/src/time.rs +++ b/library/std/src/time.rs @@ -409,8 +409,7 @@ impl Instant { } // used by platform specific `sleep_until` implementations. - // reason for #[allow(unused)]: not every platform has a specific `sleep_until`. - #[allow(unused)] + #[allow(unused, reason = "not every platform has a specific `sleep_until`")] pub(crate) fn into_inner(self) -> time::Instant { self.0 } From c1df306ad2145d714e72aa679aa25dc06b8605ed Mon Sep 17 00:00:00 2001 From: dvdsk Date: Tue, 3 Jun 2025 08:51:10 +0200 Subject: [PATCH 03/16] sleep_until: handle 32bit time_t targets --- library/std/src/sys/pal/unix/thread.rs | 17 +++++++++++------ 1 file changed, 11 insertions(+), 6 deletions(-) diff --git a/library/std/src/sys/pal/unix/thread.rs b/library/std/src/sys/pal/unix/thread.rs index 7ee7241f1000b..b460bc4f46e79 100644 --- a/library/std/src/sys/pal/unix/thread.rs +++ b/library/std/src/sys/pal/unix/thread.rs @@ -310,11 +310,17 @@ impl Thread { target_os = "vxworks", ))] pub fn sleep_until(deadline: Instant) { - let mut ts = deadline - .into_inner() - .into_timespec() - .to_timespec() - .expect("Timespec is narrower then libc::timespec thus conversion can't fail"); + let Some(mut ts) = deadline.into_inner().into_timespec().to_timespec() else { + // The deadline is further in the future then can be passed to + // clock_nanosleep. We have to use Self::sleep instead. This might + // happen on 32 bit platforms, especially closer to 2038. + let now = Instant::now(); + if let Some(delay) = deadline.checked_duration_since(now) { + Self::sleep(delay); + } + return; + }; + let ts_ptr = &mut ts as *mut _; // If we're awoken with a signal and the return value is -1 @@ -347,7 +353,6 @@ impl Thread { )))] pub fn sleep_until(deadline: Instant) { let now = Instant::now(); - if let Some(delay) = deadline.checked_duration_since(now) { Self::sleep(delay); } From fd20d20611c3b0b5381270866d814545cc61bc36 Mon Sep 17 00:00:00 2001 From: David Kleingeld Date: Thu, 5 Jun 2025 18:13:19 +0200 Subject: [PATCH 04/16] sleep_until: improve maintainability MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: Jonas Böttiger - use the internal platform specific Instant::CLOCK_ID - skip allow(unused) on on platform that uses it such that it can not become dead code - sleep_until: remove leftover links --- library/std/src/sys/pal/unix/thread.rs | 8 ++++++-- library/std/src/sys/pal/unix/time.rs | 10 +++++----- library/std/src/thread/mod.rs | 2 -- library/std/src/time.rs | 7 +++++-- 4 files changed, 16 insertions(+), 11 deletions(-) diff --git a/library/std/src/sys/pal/unix/thread.rs b/library/std/src/sys/pal/unix/thread.rs index b460bc4f46e79..713e06e1670fc 100644 --- a/library/std/src/sys/pal/unix/thread.rs +++ b/library/std/src/sys/pal/unix/thread.rs @@ -326,8 +326,12 @@ impl Thread { // 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 + while libc::clock_nanosleep( + super::time::Instant::CLOCK_ID, + libc::TIMER_ABSTIME, + ts_ptr, + ts_ptr, + ) == -1 { assert_eq!( os::errno(), diff --git a/library/std/src/sys/pal/unix/time.rs b/library/std/src/sys/pal/unix/time.rs index afce740af9f80..f34e6d1dcc968 100644 --- a/library/std/src/sys/pal/unix/time.rs +++ b/library/std/src/sys/pal/unix/time.rs @@ -261,6 +261,10 @@ pub struct Instant { } impl Instant { + #[cfg(target_vendor = "apple")] + pub(crate) const CLOCK_ID: libc::clockid_t = libc::CLOCK_UPTIME_RAW; + #[cfg(not(target_vendor = "apple"))] + pub(crate) const CLOCK_ID: libc::clockid_t = libc::CLOCK_MONOTONIC; pub fn now() -> Instant { // https://www.manpagez.com/man/3/clock_gettime/ // @@ -273,11 +277,7 @@ impl Instant { // // Instant on macos was historically implemented using mach_absolute_time; // we preserve this value domain out of an abundance of caution. - #[cfg(target_vendor = "apple")] - const clock_id: libc::clockid_t = libc::CLOCK_UPTIME_RAW; - #[cfg(not(target_vendor = "apple"))] - const clock_id: libc::clockid_t = libc::CLOCK_MONOTONIC; - Instant { t: Timespec::now(clock_id) } + Instant { t: Timespec::now(Self::CLOCK_ID) } } pub fn checked_sub_instant(&self, other: &Instant) -> Option { diff --git a/library/std/src/thread/mod.rs b/library/std/src/thread/mod.rs index 7ee6990be8bb7..6075173db47f4 100644 --- a/library/std/src/thread/mod.rs +++ b/library/std/src/thread/mod.rs @@ -920,8 +920,6 @@ pub fn sleep(dur: Duration) { /// /// [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. /// diff --git a/library/std/src/time.rs b/library/std/src/time.rs index b4d62b5688a22..979f51ad3a39e 100644 --- a/library/std/src/time.rs +++ b/library/std/src/time.rs @@ -408,8 +408,11 @@ impl Instant { self.0.checked_sub_duration(&duration).map(Instant) } - // used by platform specific `sleep_until` implementations. - #[allow(unused, reason = "not every platform has a specific `sleep_until`")] + // Used by platform specific `sleep_until` implementations such as the one used on Linux. + #[cfg_attr( + not(target_os = "linux"), + allow(unused, reason = "not every platform has a specific `sleep_until`") + )] pub(crate) fn into_inner(self) -> time::Instant { self.0 } From 9d282c9f7b1dcec52b520c1cb04e92a5c062dc46 Mon Sep 17 00:00:00 2001 From: dvdsk Date: Thu, 12 Jun 2025 01:07:28 +0200 Subject: [PATCH 05/16] sleep_until: Fix error handling + improve maintainablity - In contradiction to nanosleep clock_nanosleep returns the error directly and does not require a call to `os::errno()`. - The last argument to clock_nanosleep can be NULL removing the need for mutating the timespec. - Missed an `allow(unused)` that could be made conditional. --- library/std/src/sys/pal/unix/thread.rs | 35 ++++++++++++++------------ library/std/src/sys/pal/unix/time.rs | 5 +++- 2 files changed, 23 insertions(+), 17 deletions(-) diff --git a/library/std/src/sys/pal/unix/thread.rs b/library/std/src/sys/pal/unix/thread.rs index 713e06e1670fc..3e4585c7187d1 100644 --- a/library/std/src/sys/pal/unix/thread.rs +++ b/library/std/src/sys/pal/unix/thread.rs @@ -310,7 +310,7 @@ impl Thread { target_os = "vxworks", ))] pub fn sleep_until(deadline: Instant) { - let Some(mut ts) = deadline.into_inner().into_timespec().to_timespec() else { + let Some(ts) = deadline.into_inner().into_timespec().to_timespec() else { // The deadline is further in the future then can be passed to // clock_nanosleep. We have to use Self::sleep instead. This might // happen on 32 bit platforms, especially closer to 2038. @@ -321,23 +321,26 @@ impl Thread { return; }; - 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( - super::time::Instant::CLOCK_ID, - libc::TIMER_ABSTIME, - ts_ptr, - ts_ptr, - ) == -1 - { - assert_eq!( - os::errno(), - libc::EINTR, - "clock nanosleep should only return an error if interrupted" + // When we get interrupted (res = EINTR) call clock_nanosleep again + loop { + let res = libc::clock_nanosleep( + super::time::Instant::CLOCK_ID, + libc::TIMER_ABSTIME, + &ts, + core::ptr::null_mut(), // not required with TIMER_ABSTIME ); + + if res == 0 { + break; + } else { + assert_eq!( + res, + libc::EINTR, + "timespec is in range, + clockid is valid and kernel should support it" + ); + } } } } diff --git a/library/std/src/sys/pal/unix/time.rs b/library/std/src/sys/pal/unix/time.rs index f34e6d1dcc968..bd7f74fea6a9c 100644 --- a/library/std/src/sys/pal/unix/time.rs +++ b/library/std/src/sys/pal/unix/time.rs @@ -292,7 +292,10 @@ impl Instant { Some(Instant { t: self.t.checked_sub_duration(other)? }) } - #[allow(unused, reason = "needed by the `sleep_until` on some unix platforms")] + #[cfg_attr( + not(target_os = "linux"), + allow(unused, reason = "needed by the `sleep_until` on some unix platforms") + )] pub(crate) fn into_timespec(self) -> Timespec { self.t } From e69a4908a3d041659066b456c070c0ca06f8e4f6 Mon Sep 17 00:00:00 2001 From: dvdsk Date: Wed, 2 Jul 2025 14:47:55 +0200 Subject: [PATCH 06/16] fixes missing import for platform unsupported --- library/std/src/sys/pal/unsupported/thread.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/library/std/src/sys/pal/unsupported/thread.rs b/library/std/src/sys/pal/unsupported/thread.rs index 9e52d5c94bc68..8a3119fa292d1 100644 --- a/library/std/src/sys/pal/unsupported/thread.rs +++ b/library/std/src/sys/pal/unsupported/thread.rs @@ -2,7 +2,7 @@ use super::unsupported; use crate::ffi::CStr; use crate::io; use crate::num::NonZero; -use crate::time::Duration; +use crate::time::{Duration, Instant}; pub struct Thread(!); From ec84a222c4f58a4d79757156813dd1104d667688 Mon Sep 17 00:00:00 2001 From: dvdsk Date: Thu, 3 Jul 2025 17:22:37 +0200 Subject: [PATCH 07/16] MIRI add clock_nanosleep support This is intended to support the std's new sleep_until. Only the clocks REALTIME and MONOTONIC are supported. The first because it was trivial to add the second as its needed for sleep_until. Only passing no flags or passing only TIMER_ABSTIME is supported. If an unsupported flags or clocks are passed this implementation panics. --- library/std/src/sys/pal/unix/thread.rs | 1 + src/tools/miri/src/shims/time.rs | 65 +++++++++++++++++++ .../miri/src/shims/unix/foreign_items.rs | 22 +++++++ 3 files changed, 88 insertions(+) diff --git a/library/std/src/sys/pal/unix/thread.rs b/library/std/src/sys/pal/unix/thread.rs index 3e4585c7187d1..53f0d1eeda5bf 100644 --- a/library/std/src/sys/pal/unix/thread.rs +++ b/library/std/src/sys/pal/unix/thread.rs @@ -297,6 +297,7 @@ impl Thread { } // Any unix that has clock_nanosleep + // If this list changes update the MIRI chock_nanosleep shim #[cfg(any( target_os = "freebsd", target_os = "netbsd", diff --git a/src/tools/miri/src/shims/time.rs b/src/tools/miri/src/shims/time.rs index 28f4ca5bb1b76..4eec124695b07 100644 --- a/src/tools/miri/src/shims/time.rs +++ b/src/tools/miri/src/shims/time.rs @@ -362,6 +362,71 @@ pub trait EvalContextExt<'tcx>: crate::MiriInterpCxExt<'tcx> { interp_ok(Scalar::from_i32(0)) } + fn clock_nanosleep( + &mut self, + clock_id: &OpTy<'tcx>, + flags: &OpTy<'tcx>, + req_op: &OpTy<'tcx>, + _rem: &OpTy<'tcx>, // Signal handlers are not supported, so rem will never be written to. + ) -> InterpResult<'tcx, Scalar> { + let this = self.eval_context_mut(); + + let clock_id: libc::clockid_t = this.read_scalar(clock_id)?.to_i32()?; + match clock_id { + libc::CLOCK_MONOTONIC => (), + libc::CLOCK_REALTIME + | libc::CLOCK_TAI + | libc::CLOCK_BOOTTIME + | libc::CLOCK_PROCESS_CPUTIME_ID => { + // The standard lib through sleep_until only needs CLOCK_MONOTONIC + panic!("MIRI only supports CLOCK_MONOTONIC for clock_nanosleep") + } + _other => return this.set_last_error_and_return_i32(LibcError("EINVAL")), + } + + let req = this.deref_pointer_as(req_op, this.libc_ty_layout("timespec"))?; + let duration = match this.read_timespec(&req)? { + Some(duration) => duration, + None => { + return this.set_last_error_and_return_i32(LibcError("EINVAL")); + } + }; + + let flags: libc::c_int = this.read_scalar(flags)?.to_i32()?; + if flags == 0 { + this.block_thread( + BlockReason::Sleep, + Some((TimeoutClock::Monotonic, TimeoutAnchor::Relative, duration)), + callback!( + @capture<'tcx> {} + |_this, unblock: UnblockKind| { + assert_eq!(unblock, UnblockKind::TimedOut); + interp_ok(()) + } + ), + ); + interp_ok(Scalar::from_i32(0)) + } else if flags == libc::TIMER_ABSTIME { + this.block_thread( + BlockReason::Sleep, + Some((TimeoutClock::Monotonic, TimeoutAnchor::Absolute, duration)), + // PR Author review note: no idea what this does, I copied it + // form nanosleep, please check carefully if it is correct + callback!( + @capture<'tcx> {} + |_this, unblock: UnblockKind| { + assert_eq!(unblock, UnblockKind::TimedOut); + interp_ok(()) + } + ), + ); + interp_ok(Scalar::from_i32(0)) + } else { + // The standard lib through sleep_until only needs TIMER_ABSTIME + panic!("MIRI only supports no flags (0) or flag TIMER_ABSTIME for clock_nanosleep") + } + } + #[allow(non_snake_case)] fn Sleep(&mut self, timeout: &OpTy<'tcx>) -> InterpResult<'tcx> { let this = self.eval_context_mut(); diff --git a/src/tools/miri/src/shims/unix/foreign_items.rs b/src/tools/miri/src/shims/unix/foreign_items.rs index b3c58397a02bd..ea2dbf28a7aca 100644 --- a/src/tools/miri/src/shims/unix/foreign_items.rs +++ b/src/tools/miri/src/shims/unix/foreign_items.rs @@ -967,6 +967,28 @@ pub trait EvalContextExt<'tcx>: crate::MiriInterpCxExt<'tcx> { let result = this.nanosleep(req, rem)?; this.write_scalar(result, dest)?; } + "clock_nanosleep" => { + // Currently this function does not exist on all Unixes, e.g. on macOS. + this.check_target_os( + &[ + "freebsd", + "netbsd", + "linux", + "android", + "solaris", + "illumos", + "dragonfly", + "hurd", + "fuchsia", + "vxworks", + ], + link_name, + )?; + let [clock_id, flags, req, rem] = + this.check_shim(abi, CanonAbi::C, link_name, args)?; + let result = this.clock_nanosleep(clock_id, flags, req, rem)?; + this.write_scalar(result, dest)?; + } "sched_getaffinity" => { // Currently this function does not exist on all Unixes, e.g. on macOS. this.check_target_os(&["linux", "freebsd", "android"], link_name)?; From dcb35932962649490191aeadab598183d388b5eb Mon Sep 17 00:00:00 2001 From: dvdsk Date: Thu, 3 Jul 2025 21:04:17 +0200 Subject: [PATCH 08/16] Adhere to Miri code standards, fixes using host constants/types --- src/tools/miri/src/shims/time.rs | 85 +++++++++---------- .../miri/src/shims/unix/foreign_items.rs | 13 +-- 2 files changed, 41 insertions(+), 57 deletions(-) diff --git a/src/tools/miri/src/shims/time.rs b/src/tools/miri/src/shims/time.rs index 4eec124695b07..f1b795a7e3fa9 100644 --- a/src/tools/miri/src/shims/time.rs +++ b/src/tools/miri/src/shims/time.rs @@ -366,25 +366,25 @@ pub trait EvalContextExt<'tcx>: crate::MiriInterpCxExt<'tcx> { &mut self, clock_id: &OpTy<'tcx>, flags: &OpTy<'tcx>, - req_op: &OpTy<'tcx>, - _rem: &OpTy<'tcx>, // Signal handlers are not supported, so rem will never be written to. + req: &OpTy<'tcx>, + rem: &OpTy<'tcx>, // Signal handlers are not supported, so rem will never be written to. ) -> InterpResult<'tcx, Scalar> { let this = self.eval_context_mut(); - let clock_id: libc::clockid_t = this.read_scalar(clock_id)?.to_i32()?; - match clock_id { - libc::CLOCK_MONOTONIC => (), - libc::CLOCK_REALTIME - | libc::CLOCK_TAI - | libc::CLOCK_BOOTTIME - | libc::CLOCK_PROCESS_CPUTIME_ID => { - // The standard lib through sleep_until only needs CLOCK_MONOTONIC - panic!("MIRI only supports CLOCK_MONOTONIC for clock_nanosleep") - } - _other => return this.set_last_error_and_return_i32(LibcError("EINVAL")), + let clockid_t_size = this.libc_ty_layout("clockid_t").size; + let clock_id = this.read_scalar(clock_id_op)?.to_int(clockid_t_size)?; + let req = this.deref_pointer_as(req_op, this.libc_ty_layout("timespec"))?; + // TODO must be a better way to do this, also fix the + // if compare of the flags later + let int_size = this.libc_ty_layout("int").size; + let flags = this.read_scalar(flags)?.to_int(int_size); + let rem = this.read_pointer()?; + + // The standard lib through sleep_until only needs CLOCK_MONOTONIC + if clock_id != this.eval_libc("CLOCK_MONOTONIC").to_int(clockid_t_size)? { + throw_unsup_format!("clock_nanosleep: only CLOCK_MONOTONIC is supported"); } - let req = this.deref_pointer_as(req_op, this.libc_ty_layout("timespec"))?; let duration = match this.read_timespec(&req)? { Some(duration) => duration, None => { @@ -392,39 +392,34 @@ pub trait EvalContextExt<'tcx>: crate::MiriInterpCxExt<'tcx> { } }; - let flags: libc::c_int = this.read_scalar(flags)?.to_i32()?; - if flags == 0 { - this.block_thread( - BlockReason::Sleep, - Some((TimeoutClock::Monotonic, TimeoutAnchor::Relative, duration)), - callback!( - @capture<'tcx> {} - |_this, unblock: UnblockKind| { - assert_eq!(unblock, UnblockKind::TimedOut); - interp_ok(()) - } - ), - ); - interp_ok(Scalar::from_i32(0)) - } else if flags == libc::TIMER_ABSTIME { - this.block_thread( - BlockReason::Sleep, - Some((TimeoutClock::Monotonic, TimeoutAnchor::Absolute, duration)), - // PR Author review note: no idea what this does, I copied it - // form nanosleep, please check carefully if it is correct - callback!( - @capture<'tcx> {} - |_this, unblock: UnblockKind| { - assert_eq!(unblock, UnblockKind::TimedOut); - interp_ok(()) - } - ), - ); - interp_ok(Scalar::from_i32(0)) + let timeout_anchor = if flags == 0 { + // No flags set, the timespec should be interperted as a duration + // to sleep for + TimeoutAnchor::Relative + } else if flag == this.eval_libc("TIMER_ABSTIME").to_int(int_size) { + // Only flag TIMER_ABSTIME set, the timespec should be interperted as + // an absolute time. + TimeoutAnchor::Absolute } else { // The standard lib through sleep_until only needs TIMER_ABSTIME - panic!("MIRI only supports no flags (0) or flag TIMER_ABSTIME for clock_nanosleep") - } + throw_unsup_format!( + "`clock_nanosleep` unsupported flags {flags}, only no flags or\ + TIMER_ABSTIME is supported" + ); + }; + + this.block_thread( + BlockReason::Sleep, + Some((TimeoutClock::Monotonic, timeout_anchor, duration)), + callback!( + @capture<'tcx> {} + |_this, unblock: UnblockKind| { + assert_eq!(unblock, UnblockKind::TimedOut); + interp_ok(()) + } + ), + ); + interp_ok(Scalar::from_i32(0)) } #[allow(non_snake_case)] diff --git a/src/tools/miri/src/shims/unix/foreign_items.rs b/src/tools/miri/src/shims/unix/foreign_items.rs index ea2dbf28a7aca..5f3778d967e7e 100644 --- a/src/tools/miri/src/shims/unix/foreign_items.rs +++ b/src/tools/miri/src/shims/unix/foreign_items.rs @@ -970,18 +970,7 @@ pub trait EvalContextExt<'tcx>: crate::MiriInterpCxExt<'tcx> { "clock_nanosleep" => { // Currently this function does not exist on all Unixes, e.g. on macOS. this.check_target_os( - &[ - "freebsd", - "netbsd", - "linux", - "android", - "solaris", - "illumos", - "dragonfly", - "hurd", - "fuchsia", - "vxworks", - ], + &["freebsd", "linux", "android", "solaris", "illumos"], link_name, )?; let [clock_id, flags, req, rem] = From f07f99c01743a933937834d0392af038bc43fb30 Mon Sep 17 00:00:00 2001 From: dvdsk Date: Thu, 3 Jul 2025 22:16:38 +0200 Subject: [PATCH 09/16] fix read_scalar/read_pointer --- src/tools/miri/src/shims/time.rs | 19 ++++++++----------- 1 file changed, 8 insertions(+), 11 deletions(-) diff --git a/src/tools/miri/src/shims/time.rs b/src/tools/miri/src/shims/time.rs index f1b795a7e3fa9..527a21607ca90 100644 --- a/src/tools/miri/src/shims/time.rs +++ b/src/tools/miri/src/shims/time.rs @@ -367,18 +367,15 @@ pub trait EvalContextExt<'tcx>: crate::MiriInterpCxExt<'tcx> { clock_id: &OpTy<'tcx>, flags: &OpTy<'tcx>, req: &OpTy<'tcx>, - rem: &OpTy<'tcx>, // Signal handlers are not supported, so rem will never be written to. + rem: &OpTy<'tcx>, ) -> InterpResult<'tcx, Scalar> { let this = self.eval_context_mut(); let clockid_t_size = this.libc_ty_layout("clockid_t").size; - let clock_id = this.read_scalar(clock_id_op)?.to_int(clockid_t_size)?; - let req = this.deref_pointer_as(req_op, this.libc_ty_layout("timespec"))?; - // TODO must be a better way to do this, also fix the - // if compare of the flags later - let int_size = this.libc_ty_layout("int").size; - let flags = this.read_scalar(flags)?.to_int(int_size); - let rem = this.read_pointer()?; + let clock_id = this.read_scalar(clock_id)?.to_int(clockid_t_size)?; + let req = this.deref_pointer_as(req, this.libc_ty_layout("timespec"))?; + let flags = this.read_scalar(flags)?.to_i32()?; + let _rem = this.read_pointer(rem)?; // Signal handlers are not supported, so rem will never be written to. // The standard lib through sleep_until only needs CLOCK_MONOTONIC if clock_id != this.eval_libc("CLOCK_MONOTONIC").to_int(clockid_t_size)? { @@ -396,14 +393,14 @@ pub trait EvalContextExt<'tcx>: crate::MiriInterpCxExt<'tcx> { // No flags set, the timespec should be interperted as a duration // to sleep for TimeoutAnchor::Relative - } else if flag == this.eval_libc("TIMER_ABSTIME").to_int(int_size) { + } else if flags == this.eval_libc("TIMER_ABSTIME").to_i32()? { // Only flag TIMER_ABSTIME set, the timespec should be interperted as // an absolute time. TimeoutAnchor::Absolute } else { - // The standard lib through sleep_until only needs TIMER_ABSTIME + // The standard lib (through `sleep_until`) only needs TIMER_ABSTIME throw_unsup_format!( - "`clock_nanosleep` unsupported flags {flags}, only no flags or\ + "`clock_nanosleep` unsupported flags {flags}, only no flags or \ TIMER_ABSTIME is supported" ); }; From 264508050f78db14ef189e96fda4281e8d8d4754 Mon Sep 17 00:00:00 2001 From: dvdsk Date: Thu, 3 Jul 2025 22:23:15 +0200 Subject: [PATCH 10/16] rename clock_nanosleep argument --- src/tools/miri/src/shims/time.rs | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/src/tools/miri/src/shims/time.rs b/src/tools/miri/src/shims/time.rs index 527a21607ca90..d3a1b9046b4b4 100644 --- a/src/tools/miri/src/shims/time.rs +++ b/src/tools/miri/src/shims/time.rs @@ -366,14 +366,14 @@ pub trait EvalContextExt<'tcx>: crate::MiriInterpCxExt<'tcx> { &mut self, clock_id: &OpTy<'tcx>, flags: &OpTy<'tcx>, - req: &OpTy<'tcx>, + timespec: &OpTy<'tcx>, rem: &OpTy<'tcx>, ) -> InterpResult<'tcx, Scalar> { let this = self.eval_context_mut(); - let clockid_t_size = this.libc_ty_layout("clockid_t").size; + let clock_id = this.read_scalar(clock_id)?.to_int(clockid_t_size)?; - let req = this.deref_pointer_as(req, this.libc_ty_layout("timespec"))?; + let timespec = this.deref_pointer_as(timespec, this.libc_ty_layout("timespec"))?; let flags = this.read_scalar(flags)?.to_i32()?; let _rem = this.read_pointer(rem)?; // Signal handlers are not supported, so rem will never be written to. @@ -382,7 +382,7 @@ pub trait EvalContextExt<'tcx>: crate::MiriInterpCxExt<'tcx> { throw_unsup_format!("clock_nanosleep: only CLOCK_MONOTONIC is supported"); } - let duration = match this.read_timespec(&req)? { + let duration = match this.read_timespec(×pec)? { Some(duration) => duration, None => { return this.set_last_error_and_return_i32(LibcError("EINVAL")); From 1ca609dc9cb9b1e61e066bf4946f508f8e2ff4ba Mon Sep 17 00:00:00 2001 From: dvdsk Date: Fri, 4 Jul 2025 20:42:37 +0200 Subject: [PATCH 11/16] use eval_libc_i32 instead of .to_i32() --- src/tools/miri/src/shims/time.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/tools/miri/src/shims/time.rs b/src/tools/miri/src/shims/time.rs index d3a1b9046b4b4..4d21fd248c893 100644 --- a/src/tools/miri/src/shims/time.rs +++ b/src/tools/miri/src/shims/time.rs @@ -393,7 +393,7 @@ pub trait EvalContextExt<'tcx>: crate::MiriInterpCxExt<'tcx> { // No flags set, the timespec should be interperted as a duration // to sleep for TimeoutAnchor::Relative - } else if flags == this.eval_libc("TIMER_ABSTIME").to_i32()? { + } else if flags == this.eval_libc_i32("TIMER_ABSTIME") { // Only flag TIMER_ABSTIME set, the timespec should be interperted as // an absolute time. TimeoutAnchor::Absolute From e5b28af42e6205036784e964f546efa4b56fa1e8 Mon Sep 17 00:00:00 2001 From: dvdsk Date: Fri, 4 Jul 2025 21:30:42 +0200 Subject: [PATCH 12/16] shim tests adds nanosleep and clock nanosleep --- .../miri/tests/pass-dep/libc/libc-time.rs | 99 ++++++++++++++++++- 1 file changed, 98 insertions(+), 1 deletion(-) diff --git a/src/tools/miri/tests/pass-dep/libc/libc-time.rs b/src/tools/miri/tests/pass-dep/libc/libc-time.rs index e53201e0bc5d1..b0c57a07558d5 100644 --- a/src/tools/miri/tests/pass-dep/libc/libc-time.rs +++ b/src/tools/miri/tests/pass-dep/libc/libc-time.rs @@ -1,13 +1,17 @@ //@ignore-target: windows # no libc time APIs on Windows //@compile-flags: -Zmiri-disable-isolation use std::{env, mem, ptr}; +use std::time::{Duration, Instant}; fn main() { test_clocks(); test_posix_gettimeofday(); + test_nanosleep(); + test_clock_nanosleep_absolute(); + test_clock_nanosleep_relative(); + test_localtime_r_epoch(); test_localtime_r_gmt(); test_localtime_r_pst(); - test_localtime_r_epoch(); #[cfg(any( target_os = "linux", target_os = "macos", @@ -60,6 +64,99 @@ fn test_posix_gettimeofday() { assert_eq!(is_error, -1); } +fn test_nanosleep() { + // sleep zero seconds + let start_zero_second_sleep = Instant::now(); + let timespec = libc::timespec { tv_sec: 0, tv_nsec: 0 }; + let remainder = ptr::null_mut::(); + let is_error = unsafe { libc::nanosleep(×pec, remainder) }; + assert_eq!(is_error, 0); + assert!(start_zero_second_sleep.elapsed() < Duration::from_millis(100)); + + // sleep one second + let start_one_second_sleep = Instant::now(); + let timespec = libc::timespec { tv_sec: 1, tv_nsec: 0 }; + let remainder = ptr::null_mut::(); + let is_error = unsafe { libc::nanosleep(×pec, remainder) }; + assert_eq!(is_error, 0); + assert!(start_one_second_sleep.elapsed() > Duration::from_secs(1)); +} + +/// Helper function to get the current time for testing relative sleeps +fn timespec_now(clock: libc::clockid_t) -> libc::timespec { + let mut timespec = mem::MaybeUninit::::uninit(); + let is_error = unsafe { libc::clock_gettime(clock, timespec.as_mut_ptr()) }; + assert_eq!(is_error, 0); + unsafe { timespec.assume_init() } +} + +fn test_clock_nanosleep_absolute() { + let start_zero_second_sleep = Instant::now(); + let unix_time_zero = libc::timespec { tv_sec: 0, tv_nsec: 0 }; + let remainder = ptr::null_mut::(); + let error = unsafe { + // this will not sleep since unix time zero is in the past + libc::clock_nanosleep( + libc::CLOCK_MONOTONIC, + libc::TIMER_ABSTIME, + &unix_time_zero, + remainder, + ) + }; + assert_eq!(error, 0); + assert!(start_zero_second_sleep.elapsed() < Duration::from_millis(100)); + + let start_one_second_sleep = Instant::now(); + let mut one_second_from_now = timespec_now(libc::CLOCK_MONOTONIC); + one_second_from_now.tv_sec += 1; + let remainder = ptr::null_mut::(); + let error = unsafe { + // this will not sleep since unix time zero is in the past + libc::clock_nanosleep( + libc::CLOCK_MONOTONIC, + libc::TIMER_ABSTIME, + &one_second_from_now, + remainder, + ) + }; + assert_eq!(error, 0); + assert!(start_one_second_sleep.elapsed() > Duration::from_secs(1)); +} + +fn test_clock_nanosleep_relative() { + const NO_FLAGS: i32 = 0; + + let start_zero_second_sleep = Instant::now(); + let zero_seconds = libc::timespec { tv_sec: 0, tv_nsec: 0 }; + let remainder = ptr::null_mut::(); + let error = unsafe { + // this will not sleep since unix time zero is in the past + libc::clock_nanosleep( + libc::CLOCK_MONOTONIC, + NO_FLAGS, + &zero_seconds, + remainder, + ) + }; + assert_eq!(error, 0); + assert!(start_zero_second_sleep.elapsed() < Duration::from_millis(100)); + + let start_one_second_sleep = Instant::now(); + let one_second = libc::timespec { tv_sec: 1, tv_nsec: 0 }; + let remainder = ptr::null_mut::(); + let error = unsafe { + // this will not sleep since unix time zero is in the past + libc::clock_nanosleep( + libc::CLOCK_MONOTONIC, + NO_FLAGS, + &one_second, + remainder, + ) + }; + assert_eq!(error, 0); + assert!(start_one_second_sleep.elapsed() > Duration::from_secs(1)); +} + /// Helper function to create an empty tm struct. fn create_empty_tm() -> libc::tm { libc::tm { From c5a13dda1a428af15a19e49d16025cc8428ced3c Mon Sep 17 00:00:00 2001 From: dvdsk Date: Fri, 4 Jul 2025 21:35:22 +0200 Subject: [PATCH 13/16] add sleep_until test --- src/tools/miri/tests/pass/shims/time.rs | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/src/tools/miri/tests/pass/shims/time.rs b/src/tools/miri/tests/pass/shims/time.rs index 226f04ade0f41..b16286fd1a00c 100644 --- a/src/tools/miri/tests/pass/shims/time.rs +++ b/src/tools/miri/tests/pass/shims/time.rs @@ -1,4 +1,5 @@ //@compile-flags: -Zmiri-disable-isolation +#![feature(thread_sleep_until)] use std::time::{Duration, Instant, SystemTime}; @@ -15,6 +16,14 @@ fn test_sleep() { assert!((after - before).as_millis() >= 100); } +fn test_sleep_until() { + let before = Instant::now(); + let one_second_from_now = before + Duration::from_millis(100); + std::thread::sleep_until(one_second_from_now); + let after = Instant::now(); + assert!((after - before).as_millis() >= 100); +} + fn main() { // Check `SystemTime`. let now1 = SystemTime::now(); @@ -49,4 +58,5 @@ fn main() { duration_sanity(diff); test_sleep(); + test_sleep_until(); } From dbe9e85b39f5c01d230b0992f5e8a6bfd2dfe3c1 Mon Sep 17 00:00:00 2001 From: dvdsk Date: Fri, 4 Jul 2025 21:55:15 +0200 Subject: [PATCH 14/16] change test sleep duration from 1s to 100ms --- .../miri/tests/pass-dep/libc/libc-time.rs | 73 +++++++------------ src/tools/miri/tests/pass/shims/time.rs | 4 +- 2 files changed, 30 insertions(+), 47 deletions(-) diff --git a/src/tools/miri/tests/pass-dep/libc/libc-time.rs b/src/tools/miri/tests/pass-dep/libc/libc-time.rs index b0c57a07558d5..3cffcd9097b9d 100644 --- a/src/tools/miri/tests/pass-dep/libc/libc-time.rs +++ b/src/tools/miri/tests/pass-dep/libc/libc-time.rs @@ -65,21 +65,19 @@ fn test_posix_gettimeofday() { } fn test_nanosleep() { - // sleep zero seconds - let start_zero_second_sleep = Instant::now(); - let timespec = libc::timespec { tv_sec: 0, tv_nsec: 0 }; + let start_test_sleep = Instant::now(); + let duration_zero = libc::timespec { tv_sec: 0, tv_nsec: 0 }; let remainder = ptr::null_mut::(); - let is_error = unsafe { libc::nanosleep(×pec, remainder) }; + let is_error = unsafe { libc::nanosleep(&duration_zero, remainder) }; assert_eq!(is_error, 0); - assert!(start_zero_second_sleep.elapsed() < Duration::from_millis(100)); + assert!(start_test_sleep.elapsed() < Duration::from_millis(10)); - // sleep one second - let start_one_second_sleep = Instant::now(); - let timespec = libc::timespec { tv_sec: 1, tv_nsec: 0 }; + let start_test_sleep = Instant::now(); + let duration_100_millis = libc::timespec { tv_sec: 0, tv_nsec: 1_000_000_000 / 10 }; let remainder = ptr::null_mut::(); - let is_error = unsafe { libc::nanosleep(×pec, remainder) }; + let is_error = unsafe { libc::nanosleep(&duration_100_millis, remainder) }; assert_eq!(is_error, 0); - assert!(start_one_second_sleep.elapsed() > Duration::from_secs(1)); + assert!(start_test_sleep.elapsed() > Duration::from_millis(100)); } /// Helper function to get the current time for testing relative sleeps @@ -91,70 +89,55 @@ fn timespec_now(clock: libc::clockid_t) -> libc::timespec { } fn test_clock_nanosleep_absolute() { - let start_zero_second_sleep = Instant::now(); - let unix_time_zero = libc::timespec { tv_sec: 0, tv_nsec: 0 }; + let start_test_sleep = Instant::now(); + let before_start = libc::timespec { tv_sec: 0, tv_nsec: 0 }; let remainder = ptr::null_mut::(); let error = unsafe { // this will not sleep since unix time zero is in the past - libc::clock_nanosleep( - libc::CLOCK_MONOTONIC, - libc::TIMER_ABSTIME, - &unix_time_zero, - remainder, - ) + libc::clock_nanosleep(libc::CLOCK_MONOTONIC, libc::TIMER_ABSTIME, &before_start, remainder) }; assert_eq!(error, 0); - assert!(start_zero_second_sleep.elapsed() < Duration::from_millis(100)); + assert!(start_test_sleep.elapsed() < Duration::from_millis(10)); - let start_one_second_sleep = Instant::now(); - let mut one_second_from_now = timespec_now(libc::CLOCK_MONOTONIC); - one_second_from_now.tv_sec += 1; + let start_test_sleep = Instant::now(); + let hunderd_millis_after_start = { + let mut ts = timespec_now(libc::CLOCK_MONOTONIC); + ts.tv_nsec += 1_000_000_000 / 10; + ts + }; let remainder = ptr::null_mut::(); let error = unsafe { - // this will not sleep since unix time zero is in the past libc::clock_nanosleep( libc::CLOCK_MONOTONIC, libc::TIMER_ABSTIME, - &one_second_from_now, + &hunderd_millis_after_start, remainder, ) }; assert_eq!(error, 0); - assert!(start_one_second_sleep.elapsed() > Duration::from_secs(1)); + assert!(start_test_sleep.elapsed() > Duration::from_millis(100)); } fn test_clock_nanosleep_relative() { const NO_FLAGS: i32 = 0; - let start_zero_second_sleep = Instant::now(); - let zero_seconds = libc::timespec { tv_sec: 0, tv_nsec: 0 }; + let start_test_sleep = Instant::now(); + let duration_zero = libc::timespec { tv_sec: 0, tv_nsec: 0 }; let remainder = ptr::null_mut::(); let error = unsafe { - // this will not sleep since unix time zero is in the past - libc::clock_nanosleep( - libc::CLOCK_MONOTONIC, - NO_FLAGS, - &zero_seconds, - remainder, - ) + libc::clock_nanosleep(libc::CLOCK_MONOTONIC, NO_FLAGS, &duration_zero, remainder) }; assert_eq!(error, 0); - assert!(start_zero_second_sleep.elapsed() < Duration::from_millis(100)); + assert!(start_test_sleep.elapsed() < Duration::from_millis(10)); - let start_one_second_sleep = Instant::now(); - let one_second = libc::timespec { tv_sec: 1, tv_nsec: 0 }; + let start_test_sleep = Instant::now(); + let duration_100_millis = libc::timespec { tv_sec: 0, tv_nsec: 1_000_000_000 / 10 }; let remainder = ptr::null_mut::(); let error = unsafe { - // this will not sleep since unix time zero is in the past - libc::clock_nanosleep( - libc::CLOCK_MONOTONIC, - NO_FLAGS, - &one_second, - remainder, - ) + libc::clock_nanosleep(libc::CLOCK_MONOTONIC, NO_FLAGS, &duration_100_millis, remainder) }; assert_eq!(error, 0); - assert!(start_one_second_sleep.elapsed() > Duration::from_secs(1)); + assert!(start_test_sleep.elapsed() > Duration::from_millis(100)); } /// Helper function to create an empty tm struct. diff --git a/src/tools/miri/tests/pass/shims/time.rs b/src/tools/miri/tests/pass/shims/time.rs index b16286fd1a00c..ef0b400f1a716 100644 --- a/src/tools/miri/tests/pass/shims/time.rs +++ b/src/tools/miri/tests/pass/shims/time.rs @@ -18,8 +18,8 @@ fn test_sleep() { fn test_sleep_until() { let before = Instant::now(); - let one_second_from_now = before + Duration::from_millis(100); - std::thread::sleep_until(one_second_from_now); + let hunderd_millis_after_start = before + Duration::from_millis(100); + std::thread::sleep_until(hunderd_millis_after_start); let after = Instant::now(); assert!((after - before).as_millis() >= 100); } From 5521ed94f27acea82efeac8a70b0ec36bab069e8 Mon Sep 17 00:00:00 2001 From: dvdsk Date: Fri, 4 Jul 2025 23:02:15 +0200 Subject: [PATCH 15/16] only run clock_nanosleep tests on support target_os --- src/tools/miri/tests/pass-dep/libc/libc-time.rs | 15 ++++++++++++--- 1 file changed, 12 insertions(+), 3 deletions(-) diff --git a/src/tools/miri/tests/pass-dep/libc/libc-time.rs b/src/tools/miri/tests/pass-dep/libc/libc-time.rs index 3cffcd9097b9d..c54398003ffce 100644 --- a/src/tools/miri/tests/pass-dep/libc/libc-time.rs +++ b/src/tools/miri/tests/pass-dep/libc/libc-time.rs @@ -1,14 +1,23 @@ //@ignore-target: windows # no libc time APIs on Windows //@compile-flags: -Zmiri-disable-isolation -use std::{env, mem, ptr}; use std::time::{Duration, Instant}; +use std::{env, mem, ptr}; fn main() { test_clocks(); test_posix_gettimeofday(); test_nanosleep(); - test_clock_nanosleep_absolute(); - test_clock_nanosleep_relative(); + #[cfg(any( + target_os = "freebsd", + target_os = "linux", + target_os = "android", + target_os = "solaris", + target_os = "illumos" + ))] + { + test_clock_nanosleep_absolute(); + test_clock_nanosleep_relative(); + } test_localtime_r_epoch(); test_localtime_r_gmt(); test_localtime_r_pst(); From d3f167f1fe68023c1530dd0d7b14cd22270dba5d Mon Sep 17 00:00:00 2001 From: dvdsk Date: Sat, 5 Jul 2025 12:02:39 +0200 Subject: [PATCH 16/16] clock_nanosleep tests behind cfg + handle tv.nsec > 1s --- .../miri/tests/pass-dep/libc/libc-time.rs | 201 ++++++++++-------- 1 file changed, 112 insertions(+), 89 deletions(-) diff --git a/src/tools/miri/tests/pass-dep/libc/libc-time.rs b/src/tools/miri/tests/pass-dep/libc/libc-time.rs index c54398003ffce..b618f93246c96 100644 --- a/src/tools/miri/tests/pass-dep/libc/libc-time.rs +++ b/src/tools/miri/tests/pass-dep/libc/libc-time.rs @@ -6,21 +6,9 @@ use std::{env, mem, ptr}; fn main() { test_clocks(); test_posix_gettimeofday(); - test_nanosleep(); - #[cfg(any( - target_os = "freebsd", - target_os = "linux", - target_os = "android", - target_os = "solaris", - target_os = "illumos" - ))] - { - test_clock_nanosleep_absolute(); - test_clock_nanosleep_relative(); - } - test_localtime_r_epoch(); test_localtime_r_gmt(); test_localtime_r_pst(); + test_localtime_r_epoch(); #[cfg(any( target_os = "linux", target_os = "macos", @@ -33,6 +21,19 @@ fn main() { test_localtime_r_future_32b(); #[cfg(target_pointer_width = "64")] test_localtime_r_future_64b(); + + test_nanosleep(); + #[cfg(any( + target_os = "freebsd", + target_os = "linux", + target_os = "android", + target_os = "solaris", + target_os = "illumos" + ))] + { + test_clock_nanosleep::absolute(); + test_clock_nanosleep::relative(); + } } /// Tests whether clock support exists at all @@ -73,82 +74,6 @@ fn test_posix_gettimeofday() { assert_eq!(is_error, -1); } -fn test_nanosleep() { - let start_test_sleep = Instant::now(); - let duration_zero = libc::timespec { tv_sec: 0, tv_nsec: 0 }; - let remainder = ptr::null_mut::(); - let is_error = unsafe { libc::nanosleep(&duration_zero, remainder) }; - assert_eq!(is_error, 0); - assert!(start_test_sleep.elapsed() < Duration::from_millis(10)); - - let start_test_sleep = Instant::now(); - let duration_100_millis = libc::timespec { tv_sec: 0, tv_nsec: 1_000_000_000 / 10 }; - let remainder = ptr::null_mut::(); - let is_error = unsafe { libc::nanosleep(&duration_100_millis, remainder) }; - assert_eq!(is_error, 0); - assert!(start_test_sleep.elapsed() > Duration::from_millis(100)); -} - -/// Helper function to get the current time for testing relative sleeps -fn timespec_now(clock: libc::clockid_t) -> libc::timespec { - let mut timespec = mem::MaybeUninit::::uninit(); - let is_error = unsafe { libc::clock_gettime(clock, timespec.as_mut_ptr()) }; - assert_eq!(is_error, 0); - unsafe { timespec.assume_init() } -} - -fn test_clock_nanosleep_absolute() { - let start_test_sleep = Instant::now(); - let before_start = libc::timespec { tv_sec: 0, tv_nsec: 0 }; - let remainder = ptr::null_mut::(); - let error = unsafe { - // this will not sleep since unix time zero is in the past - libc::clock_nanosleep(libc::CLOCK_MONOTONIC, libc::TIMER_ABSTIME, &before_start, remainder) - }; - assert_eq!(error, 0); - assert!(start_test_sleep.elapsed() < Duration::from_millis(10)); - - let start_test_sleep = Instant::now(); - let hunderd_millis_after_start = { - let mut ts = timespec_now(libc::CLOCK_MONOTONIC); - ts.tv_nsec += 1_000_000_000 / 10; - ts - }; - let remainder = ptr::null_mut::(); - let error = unsafe { - libc::clock_nanosleep( - libc::CLOCK_MONOTONIC, - libc::TIMER_ABSTIME, - &hunderd_millis_after_start, - remainder, - ) - }; - assert_eq!(error, 0); - assert!(start_test_sleep.elapsed() > Duration::from_millis(100)); -} - -fn test_clock_nanosleep_relative() { - const NO_FLAGS: i32 = 0; - - let start_test_sleep = Instant::now(); - let duration_zero = libc::timespec { tv_sec: 0, tv_nsec: 0 }; - let remainder = ptr::null_mut::(); - let error = unsafe { - libc::clock_nanosleep(libc::CLOCK_MONOTONIC, NO_FLAGS, &duration_zero, remainder) - }; - assert_eq!(error, 0); - assert!(start_test_sleep.elapsed() < Duration::from_millis(10)); - - let start_test_sleep = Instant::now(); - let duration_100_millis = libc::timespec { tv_sec: 0, tv_nsec: 1_000_000_000 / 10 }; - let remainder = ptr::null_mut::(); - let error = unsafe { - libc::clock_nanosleep(libc::CLOCK_MONOTONIC, NO_FLAGS, &duration_100_millis, remainder) - }; - assert_eq!(error, 0); - assert!(start_test_sleep.elapsed() > Duration::from_millis(100)); -} - /// Helper function to create an empty tm struct. fn create_empty_tm() -> libc::tm { libc::tm { @@ -404,3 +329,101 @@ fn test_localtime_r_multiple_calls_deduplication() { NUM_CALLS - 1 ); } + +fn test_nanosleep() { + let start_test_sleep = Instant::now(); + let duration_zero = libc::timespec { tv_sec: 0, tv_nsec: 0 }; + let remainder = ptr::null_mut::(); + let is_error = unsafe { libc::nanosleep(&duration_zero, remainder) }; + assert_eq!(is_error, 0); + assert!(start_test_sleep.elapsed() < Duration::from_millis(10)); + + let start_test_sleep = Instant::now(); + let duration_100_millis = libc::timespec { tv_sec: 0, tv_nsec: 1_000_000_000 / 10 }; + let remainder = ptr::null_mut::(); + let is_error = unsafe { libc::nanosleep(&duration_100_millis, remainder) }; + assert_eq!(is_error, 0); + assert!(start_test_sleep.elapsed() > Duration::from_millis(100)); +} + +#[cfg(any( + target_os = "freebsd", + target_os = "linux", + target_os = "android", + target_os = "solaris", + target_os = "illumos" +))] +mod test_clock_nanosleep { + use super::*; + + pub fn absolute() { + let start_test_sleep = Instant::now(); + let before_start = libc::timespec { tv_sec: 0, tv_nsec: 0 }; + let remainder = ptr::null_mut::(); + let error = unsafe { + // this will not sleep since unix time zero is in the past + libc::clock_nanosleep( + libc::CLOCK_MONOTONIC, + libc::TIMER_ABSTIME, + &before_start, + remainder, + ) + }; + assert_eq!(error, 0); + assert!(start_test_sleep.elapsed() < Duration::from_millis(10)); + + let start_test_sleep = Instant::now(); + let hunderd_millis_after_start = add_100_millis(timespec_now(libc::CLOCK_MONOTONIC)); + let remainder = ptr::null_mut::(); + let error = unsafe { + libc::clock_nanosleep( + libc::CLOCK_MONOTONIC, + libc::TIMER_ABSTIME, + &hunderd_millis_after_start, + remainder, + ) + }; + assert_eq!(error, 0); + assert!(start_test_sleep.elapsed() > Duration::from_millis(100)); + } + + pub fn relative() { + const NO_FLAGS: i32 = 0; + + let start_test_sleep = Instant::now(); + let duration_zero = libc::timespec { tv_sec: 0, tv_nsec: 0 }; + let remainder = ptr::null_mut::(); + let error = unsafe { + libc::clock_nanosleep(libc::CLOCK_MONOTONIC, NO_FLAGS, &duration_zero, remainder) + }; + assert_eq!(error, 0); + assert!(start_test_sleep.elapsed() < Duration::from_millis(10)); + + let start_test_sleep = Instant::now(); + let duration_100_millis = libc::timespec { tv_sec: 0, tv_nsec: 1_000_000_000 / 10 }; + let remainder = ptr::null_mut::(); + let error = unsafe { + libc::clock_nanosleep(libc::CLOCK_MONOTONIC, NO_FLAGS, &duration_100_millis, remainder) + }; + assert_eq!(error, 0); + assert!(start_test_sleep.elapsed() > Duration::from_millis(100)); + } + + /// Helper function to get the current time for testing relative sleeps + fn timespec_now(clock: libc::clockid_t) -> libc::timespec { + let mut timespec = mem::MaybeUninit::::uninit(); + let is_error = unsafe { libc::clock_gettime(clock, timespec.as_mut_ptr()) }; + assert_eq!(is_error, 0); + unsafe { timespec.assume_init() } + } + + /// Helper function used to create an instant in the future + fn add_100_millis(mut ts: libc::timespec) -> libc::timespec { + const SECOND: i64 = 1_000_000_000; + ts.tv_nsec += ts.tv_nsec + SECOND / 10; + ts.tv_nsec = ts.tv_nsec % SECOND; + ts.tv_sec = ts.tv_nsec / SECOND; + ts + } +} +