Skip to content

Commit 61cf174

Browse files
committed
sleep_until: add clock_nanosleep support to Miri
The clock_nanosleep support is there to allow code using `sleep_until` to run under Miri. Therefore the implementation is minimal. - Only the clocks REALTIME and MONOTONIC are supported. The first is supported simply because it was trivial to add not because it was needed for sleep_until. - The only supported flag combinations are no flags or TIMER_ABSTIME only. If an unsupported flag combination or clock is passed in this throws unsupported.
1 parent f24ee2c commit 61cf174

File tree

5 files changed

+193
-0
lines changed

5 files changed

+193
-0
lines changed

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

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -297,6 +297,7 @@ impl Thread {
297297
}
298298

299299
// Any unix that has clock_nanosleep
300+
// If this list changes update the MIRI chock_nanosleep shim
300301
#[cfg(any(
301302
target_os = "freebsd",
302303
target_os = "netbsd",

src/tools/miri/src/shims/time.rs

Lines changed: 57 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -362,6 +362,63 @@ pub trait EvalContextExt<'tcx>: crate::MiriInterpCxExt<'tcx> {
362362
interp_ok(Scalar::from_i32(0))
363363
}
364364

365+
fn clock_nanosleep(
366+
&mut self,
367+
clock_id: &OpTy<'tcx>,
368+
flags: &OpTy<'tcx>,
369+
timespec: &OpTy<'tcx>,
370+
rem: &OpTy<'tcx>,
371+
) -> InterpResult<'tcx, Scalar> {
372+
let this = self.eval_context_mut();
373+
let clockid_t_size = this.libc_ty_layout("clockid_t").size;
374+
375+
let clock_id = this.read_scalar(clock_id)?.to_int(clockid_t_size)?;
376+
let timespec = this.deref_pointer_as(timespec, this.libc_ty_layout("timespec"))?;
377+
let flags = this.read_scalar(flags)?.to_i32()?;
378+
let _rem = this.read_pointer(rem)?; // Signal handlers are not supported, so rem will never be written to.
379+
380+
// The standard lib through sleep_until only needs CLOCK_MONOTONIC
381+
if clock_id != this.eval_libc("CLOCK_MONOTONIC").to_int(clockid_t_size)? {
382+
throw_unsup_format!("clock_nanosleep: only CLOCK_MONOTONIC is supported");
383+
}
384+
385+
let duration = match this.read_timespec(&timespec)? {
386+
Some(duration) => duration,
387+
None => {
388+
return this.set_last_error_and_return_i32(LibcError("EINVAL"));
389+
}
390+
};
391+
392+
let timeout_anchor = if flags == 0 {
393+
// No flags set, the timespec should be interperted as a duration
394+
// to sleep for
395+
TimeoutAnchor::Relative
396+
} else if flags == this.eval_libc_i32("TIMER_ABSTIME") {
397+
// Only flag TIMER_ABSTIME set, the timespec should be interperted as
398+
// an absolute time.
399+
TimeoutAnchor::Absolute
400+
} else {
401+
// The standard lib (through `sleep_until`) only needs TIMER_ABSTIME
402+
throw_unsup_format!(
403+
"`clock_nanosleep` unsupported flags {flags}, only no flags or \
404+
TIMER_ABSTIME is supported"
405+
);
406+
};
407+
408+
this.block_thread(
409+
BlockReason::Sleep,
410+
Some((TimeoutClock::Monotonic, timeout_anchor, duration)),
411+
callback!(
412+
@capture<'tcx> {}
413+
|_this, unblock: UnblockKind| {
414+
assert_eq!(unblock, UnblockKind::TimedOut);
415+
interp_ok(())
416+
}
417+
),
418+
);
419+
interp_ok(Scalar::from_i32(0))
420+
}
421+
365422
#[allow(non_snake_case)]
366423
fn Sleep(&mut self, timeout: &OpTy<'tcx>) -> InterpResult<'tcx> {
367424
let this = self.eval_context_mut();

src/tools/miri/src/shims/unix/foreign_items.rs

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -967,6 +967,17 @@ pub trait EvalContextExt<'tcx>: crate::MiriInterpCxExt<'tcx> {
967967
let result = this.nanosleep(req, rem)?;
968968
this.write_scalar(result, dest)?;
969969
}
970+
"clock_nanosleep" => {
971+
// Currently this function does not exist on all Unixes, e.g. on macOS.
972+
this.check_target_os(
973+
&["freebsd", "linux", "android", "solaris", "illumos"],
974+
link_name,
975+
)?;
976+
let [clock_id, flags, req, rem] =
977+
this.check_shim(abi, CanonAbi::C, link_name, args)?;
978+
let result = this.clock_nanosleep(clock_id, flags, req, rem)?;
979+
this.write_scalar(result, dest)?;
980+
}
970981
"sched_getaffinity" => {
971982
// Currently this function does not exist on all Unixes, e.g. on macOS.
972983
this.check_target_os(&["linux", "freebsd", "android"], link_name)?;

src/tools/miri/tests/pass-dep/libc/libc-time.rs

Lines changed: 114 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
//@ignore-target: windows # no libc time APIs on Windows
22
//@compile-flags: -Zmiri-disable-isolation
3+
use std::time::{Duration, Instant};
34
use std::{env, mem, ptr};
45

56
fn main() {
@@ -20,6 +21,19 @@ fn main() {
2021
test_localtime_r_future_32b();
2122
#[cfg(target_pointer_width = "64")]
2223
test_localtime_r_future_64b();
24+
25+
test_nanosleep();
26+
#[cfg(any(
27+
target_os = "freebsd",
28+
target_os = "linux",
29+
target_os = "android",
30+
target_os = "solaris",
31+
target_os = "illumos"
32+
))]
33+
{
34+
test_clock_nanosleep::absolute();
35+
test_clock_nanosleep::relative();
36+
}
2337
}
2438

2539
/// Tests whether clock support exists at all
@@ -315,3 +329,103 @@ fn test_localtime_r_multiple_calls_deduplication() {
315329
NUM_CALLS - 1
316330
);
317331
}
332+
333+
fn test_nanosleep() {
334+
let start_test_sleep = Instant::now();
335+
let duration_zero = libc::timespec { tv_sec: 0, tv_nsec: 0 };
336+
let remainder = ptr::null_mut::<libc::timespec>();
337+
let is_error = unsafe { libc::nanosleep(&duration_zero, remainder) };
338+
assert_eq!(is_error, 0);
339+
assert!(start_test_sleep.elapsed() < Duration::from_millis(10));
340+
341+
let start_test_sleep = Instant::now();
342+
let duration_100_millis = libc::timespec { tv_sec: 0, tv_nsec: 1_000_000_000 / 10 };
343+
let remainder = ptr::null_mut::<libc::timespec>();
344+
let is_error = unsafe { libc::nanosleep(&duration_100_millis, remainder) };
345+
assert_eq!(is_error, 0);
346+
assert!(start_test_sleep.elapsed() > Duration::from_millis(100));
347+
}
348+
349+
#[cfg(any(
350+
target_os = "freebsd",
351+
target_os = "linux",
352+
target_os = "android",
353+
target_os = "solaris",
354+
target_os = "illumos"
355+
))]
356+
mod test_clock_nanosleep {
357+
use super::*;
358+
359+
/// Helper function used to create an instant in the future
360+
fn add_100_millis(mut ts: libc::timespec) -> libc::timespec {
361+
// While tv_nsec has type `c_long` tv_sec has type `time_t`. These might
362+
// end up as different types (for example: like i32 and i64).
363+
const SECOND: libc::c_long = 1_000_000_000;
364+
ts.tv_nsec += SECOND / 10;
365+
// If this pushes tv_nsec to SECOND or higher, we need to overflow to tv_sec.
366+
ts.tv_sec += (ts.tv_nsec / SECOND) as libc::time_t;
367+
ts.tv_nsec %= SECOND;
368+
ts
369+
}
370+
371+
/// Helper function to get the current time for testing relative sleeps
372+
fn timespec_now(clock: libc::clockid_t) -> libc::timespec {
373+
let mut timespec = mem::MaybeUninit::<libc::timespec>::uninit();
374+
let is_error = unsafe { libc::clock_gettime(clock, timespec.as_mut_ptr()) };
375+
assert_eq!(is_error, 0);
376+
unsafe { timespec.assume_init() }
377+
}
378+
379+
pub fn absolute() {
380+
let start_test_sleep = Instant::now();
381+
let before_start = libc::timespec { tv_sec: 0, tv_nsec: 0 };
382+
let remainder = ptr::null_mut::<libc::timespec>();
383+
let error = unsafe {
384+
// this will not sleep since unix time zero is in the past
385+
libc::clock_nanosleep(
386+
libc::CLOCK_MONOTONIC,
387+
libc::TIMER_ABSTIME,
388+
&before_start,
389+
remainder,
390+
)
391+
};
392+
assert_eq!(error, 0);
393+
assert!(start_test_sleep.elapsed() < Duration::from_millis(10));
394+
395+
let start_test_sleep = Instant::now();
396+
let hunderd_millis_after_start = add_100_millis(timespec_now(libc::CLOCK_MONOTONIC));
397+
let remainder = ptr::null_mut::<libc::timespec>();
398+
let error = unsafe {
399+
libc::clock_nanosleep(
400+
libc::CLOCK_MONOTONIC,
401+
libc::TIMER_ABSTIME,
402+
&hunderd_millis_after_start,
403+
remainder,
404+
)
405+
};
406+
assert_eq!(error, 0);
407+
assert!(start_test_sleep.elapsed() > Duration::from_millis(100));
408+
}
409+
410+
pub fn relative() {
411+
const NO_FLAGS: i32 = 0;
412+
413+
let start_test_sleep = Instant::now();
414+
let duration_zero = libc::timespec { tv_sec: 0, tv_nsec: 0 };
415+
let remainder = ptr::null_mut::<libc::timespec>();
416+
let error = unsafe {
417+
libc::clock_nanosleep(libc::CLOCK_MONOTONIC, NO_FLAGS, &duration_zero, remainder)
418+
};
419+
assert_eq!(error, 0);
420+
assert!(start_test_sleep.elapsed() < Duration::from_millis(10));
421+
422+
let start_test_sleep = Instant::now();
423+
let duration_100_millis = libc::timespec { tv_sec: 0, tv_nsec: 1_000_000_000 / 10 };
424+
let remainder = ptr::null_mut::<libc::timespec>();
425+
let error = unsafe {
426+
libc::clock_nanosleep(libc::CLOCK_MONOTONIC, NO_FLAGS, &duration_100_millis, remainder)
427+
};
428+
assert_eq!(error, 0);
429+
assert!(start_test_sleep.elapsed() > Duration::from_millis(100));
430+
}
431+
}

src/tools/miri/tests/pass/shims/time.rs

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
//@compile-flags: -Zmiri-disable-isolation
2+
#![feature(thread_sleep_until)]
23

34
use std::time::{Duration, Instant, SystemTime};
45

@@ -15,6 +16,14 @@ fn test_sleep() {
1516
assert!((after - before).as_millis() >= 100);
1617
}
1718

19+
fn test_sleep_until() {
20+
let before = Instant::now();
21+
let hunderd_millis_after_start = before + Duration::from_millis(100);
22+
std::thread::sleep_until(hunderd_millis_after_start);
23+
let after = Instant::now();
24+
assert!((after - before).as_millis() >= 100);
25+
}
26+
1827
fn main() {
1928
// Check `SystemTime`.
2029
let now1 = SystemTime::now();
@@ -49,4 +58,5 @@ fn main() {
4958
duration_sanity(diff);
5059

5160
test_sleep();
61+
test_sleep_until();
5262
}

0 commit comments

Comments
 (0)