Skip to content

Commit b7dba0e

Browse files
authored
Rollup merge of #161295 - joboet:sleep_until-long-elapsed, r=tgross35
std: don't panic on long-elapsed deadlines for `sleep_until` `clock_nanosleep` doesn't like deadlines before the clock's epoch, so `sleep_until` can currently panic ([playground](https://play.rust-lang.org/?version=nightly&mode=debug&edition=2024&gist=90acf91e5d4cd3bbb64187a26707d0d8)). Unfortunately the POSIX specification is quite vague about which `timespec` values are allowed, see the code comment...
2 parents 7659434 + 2d28ce9 commit b7dba0e

3 files changed

Lines changed: 35 additions & 5 deletions

File tree

library/std/src/sys/thread/unix.rs

Lines changed: 22 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -668,6 +668,26 @@ pub fn sleep(dur: Duration) {
668668
pub fn sleep_until(deadline: crate::time::Instant) {
669669
use crate::time::Instant;
670670

671+
let timespec = deadline.into_inner().into_timespec();
672+
if timespec.tv_sec < 0 {
673+
// `clock_nanosleep` fails with EINVAL if
674+
// > The tp argument to clock_settime() is outside the range for the
675+
// > given clock ID.
676+
//
677+
// This specification allows *any* clock range, which means we'd
678+
// theoretically have to detect whether the time point is in the
679+
// future (and block indefinitely) or the past (and return immediately)
680+
// when encountering `EINVAL`. But since all existing implementations
681+
// interpret this as saying that negative `tv_sec` values are unsupported,
682+
// we can just test that and return – given that POSIX specifies that
683+
// `CLOCK_MONOTONIC` measures the time "since an unspecified amount
684+
// in the past" negative values are definitely in the past. If you
685+
// observe any platform returning `EINVAL` for more cases, please
686+
// file a bug; we'd need to add logic handling `EINVAL` when it
687+
// occurs.
688+
return;
689+
}
690+
671691
#[cfg(all(
672692
target_os = "linux",
673693
target_env = "gnu",
@@ -690,7 +710,7 @@ pub fn sleep_until(deadline: crate::time::Instant) {
690710
}
691711

692712
if let Some(clock_nanosleep) = __clock_nanosleep_time64.get() {
693-
let ts = deadline.into_inner().into_timespec().to_timespec64();
713+
let ts = timespec.to_timespec64();
694714
loop {
695715
let r = unsafe {
696716
clock_nanosleep(
@@ -718,7 +738,7 @@ pub fn sleep_until(deadline: crate::time::Instant) {
718738
}
719739
}
720740

721-
let Some(ts) = deadline.into_inner().into_timespec().to_timespec() else {
741+
let Some(ts) = timespec.to_timespec() else {
722742
// The deadline is further in the future then can be passed to
723743
// clock_nanosleep. We have to use Self::sleep instead. This might
724744
// happen on 32 bit platforms, especially closer to 2038.

library/std/src/thread/functions.rs

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -295,9 +295,10 @@ pub fn sleep(dur: Duration) {
295295

296296
/// Puts the current thread to sleep until the specified deadline has passed.
297297
///
298-
/// The thread may still be asleep after the deadline specified due to
299-
/// scheduling specifics or platform-dependent functionality. It will never
300-
/// wake before.
298+
/// If the deadline has already passed at the time this function is called, it
299+
/// will return immediately. Note that the thread may still be asleep after the
300+
/// deadline specified due to scheduling specifics or platform-dependent
301+
/// functionality. It will never wake before.
301302
///
302303
/// This function is blocking, and should not be used in `async` functions.
303304
///

library/std/src/thread/tests.rs

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -333,6 +333,15 @@ fn sleep_ms_smoke() {
333333
thread::sleep(Duration::from_millis(2));
334334
}
335335

336+
#[test]
337+
fn sleep_until_elapsed() {
338+
// UNIX's `clock_nanosleep` doesn't like timeouts that are too far back.
339+
// Test that `sleep_until` returns immediately instead of panicking.
340+
// Going 10 years back should be enough to trigger any errors.
341+
let earlier = Instant::now() - Duration::from_secs(10 * 365 * 24 * 3600);
342+
thread::sleep_until(earlier);
343+
}
344+
336345
#[test]
337346
fn test_size_of_option_thread_id() {
338347
assert_eq!(size_of::<Option<ThreadId>>(), size_of::<ThreadId>());

0 commit comments

Comments
 (0)