diff --git a/library/std/src/sys/thread/unix.rs b/library/std/src/sys/thread/unix.rs index 2dbb0314cb271..831c4e0787841 100644 --- a/library/std/src/sys/thread/unix.rs +++ b/library/std/src/sys/thread/unix.rs @@ -650,6 +650,26 @@ pub fn sleep(dur: Duration) { pub fn sleep_until(deadline: crate::time::Instant) { use crate::time::Instant; + let timespec = deadline.into_inner().into_timespec(); + if timespec.tv_sec < 0 { + // `clock_nanosleep` fails with EINVAL if + // > The tp argument to clock_settime() is outside the range for the + // > given clock ID. + // + // This specification allows *any* clock range, which means we'd + // theoretically have to detect whether the time point is in the + // future (and block indefinitely) or the past (and return immediately) + // when encountering `EINVAL`. But since all existing implementations + // interpret this as saying that negative `tv_sec` values are unsupported, + // we can just test that and return – given that POSIX specifies that + // `CLOCK_MONOTONIC` measures the time "since an unspecified amount + // in the past" negative values are definitely in the past. If you + // observe any platform returning `EINVAL` for more cases, please + // file a bug; we'd need to add logic handling `EINVAL` when it + // occurs. + return; + } + #[cfg(all( target_os = "linux", target_env = "gnu", @@ -672,7 +692,7 @@ pub fn sleep_until(deadline: crate::time::Instant) { } if let Some(clock_nanosleep) = __clock_nanosleep_time64.get() { - let ts = deadline.into_inner().into_timespec().to_timespec64(); + let ts = timespec.to_timespec64(); loop { let r = unsafe { clock_nanosleep( @@ -700,7 +720,7 @@ pub fn sleep_until(deadline: crate::time::Instant) { } } - let Some(ts) = deadline.into_inner().into_timespec().to_timespec() else { + let Some(ts) = 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. diff --git a/library/std/src/thread/functions.rs b/library/std/src/thread/functions.rs index 355a00c2a95ad..918c266cdbd34 100644 --- a/library/std/src/thread/functions.rs +++ b/library/std/src/thread/functions.rs @@ -295,9 +295,10 @@ pub fn sleep(dur: Duration) { /// Puts the current thread to sleep until the specified deadline has passed. /// -/// The thread may still be asleep after the deadline specified due to -/// scheduling specifics or platform-dependent functionality. It will never -/// wake before. +/// If the deadline has already passed at the time this function is called, it +/// will return immediately. Note that the thread may still be asleep after the +/// deadline specified due to scheduling specifics or platform-dependent +/// functionality. It will never wake before. /// /// This function is blocking, and should not be used in `async` functions. /// diff --git a/library/std/src/thread/tests.rs b/library/std/src/thread/tests.rs index 78b6f7c35e8db..e88ca92218dc8 100644 --- a/library/std/src/thread/tests.rs +++ b/library/std/src/thread/tests.rs @@ -333,6 +333,15 @@ fn sleep_ms_smoke() { thread::sleep(Duration::from_millis(2)); } +#[test] +fn sleep_until_elapsed() { + // UNIX's `clock_nanosleep` doesn't like timeouts that are too far back. + // Test that `sleep_until` returns immediately instead of panicking. + // Going 10 years back should be enough to trigger any errors. + let earlier = Instant::now() - Duration::from_secs(10 * 365 * 24 * 3600); + thread::sleep_until(earlier); +} + #[test] fn test_size_of_option_thread_id() { assert_eq!(size_of::>(), size_of::());