Skip to content

Commit f1922ff

Browse files
Rollup merge of rust-lang#151004 - apple-sleep-until, r=ChrisDenton
std: implement `sleep_until` on Apple platforms On Apple platforms, `nanosleep` is internally [implemented](https://github.com/apple-oss-distributions/Libc/blob/55b54c0a0c37b3b24393b42b90a4c561d6c606b1/gen/nanosleep.c#L281) using `mach_wait_until`, a function that waits until a deadline specified in terms of `mach_absolute_time`. Since `mach_wait_until` is [public](https://github.com/apple-oss-distributions/xnu/blob/f6217f891ac0bb64f3d375211650a4c1ff8ca1ea/osfmk/mach/mach_time.h#L50-L51)[^1], we can use it to implement `sleep_until` by converting `Instant`s (which are measured against `CLOCK_UPTIME_RAW`, which is equivalent to `mach_absolute_time`) into `mach_absolute_time` values. Related tracking issue: rust-lang#113752 [^1]: It's badly documented, but it's defined in the same header as `mach_absolute_time`, which `std` used to use for `Instant` before rust-lang#116238.
2 parents 08eb24b + af269ab commit f1922ff

File tree

8 files changed

+123
-10
lines changed

8 files changed

+123
-10
lines changed

library/std/src/lib.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -326,6 +326,7 @@
326326
#![feature(const_convert)]
327327
#![feature(core_intrinsics)]
328328
#![feature(core_io_borrowed_buf)]
329+
#![feature(cstr_display)]
329330
#![feature(drop_guard)]
330331
#![feature(duration_constants)]
331332
#![feature(error_generic_member_access)]

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

Lines changed: 44 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -270,22 +270,25 @@ pub struct Instant {
270270
}
271271

272272
impl Instant {
273+
// CLOCK_UPTIME_RAW clock that increments monotonically, in the same man-
274+
// ner as CLOCK_MONOTONIC_RAW, but that does not incre-
275+
// ment while the system is asleep. The returned value
276+
// is identical to the result of mach_absolute_time()
277+
// after the appropriate mach_timebase conversion is
278+
// applied.
279+
//
280+
// We use `CLOCK_UPTIME_RAW` instead of `CLOCK_MONOTONIC` since
281+
// `CLOCK_UPTIME_RAW` is based on `mach_absolute_time`, which is the
282+
// clock that all timeouts and deadlines are measured against inside
283+
// the kernel.
273284
#[cfg(target_vendor = "apple")]
274285
pub(crate) const CLOCK_ID: libc::clockid_t = libc::CLOCK_UPTIME_RAW;
286+
275287
#[cfg(not(target_vendor = "apple"))]
276288
pub(crate) const CLOCK_ID: libc::clockid_t = libc::CLOCK_MONOTONIC;
289+
277290
pub fn now() -> Instant {
278291
// https://pubs.opengroup.org/onlinepubs/9799919799/functions/clock_getres.html
279-
//
280-
// CLOCK_UPTIME_RAW clock that increments monotonically, in the same man-
281-
// ner as CLOCK_MONOTONIC_RAW, but that does not incre-
282-
// ment while the system is asleep. The returned value
283-
// is identical to the result of mach_absolute_time()
284-
// after the appropriate mach_timebase conversion is
285-
// applied.
286-
//
287-
// Instant on macos was historically implemented using mach_absolute_time;
288-
// we preserve this value domain out of an abundance of caution.
289292
Instant { t: Timespec::now(Self::CLOCK_ID) }
290293
}
291294

@@ -308,6 +311,37 @@ impl Instant {
308311
pub(crate) fn into_timespec(self) -> Timespec {
309312
self.t
310313
}
314+
315+
/// Returns `self` converted into units of `mach_absolute_time`, or `None`
316+
/// if `self` is before the system boot time. If the conversion cannot be
317+
/// performed precisely, this ceils the result up to the nearest
318+
/// representable value.
319+
#[cfg(target_vendor = "apple")]
320+
pub fn into_mach_absolute_time_ceil(self) -> Option<u128> {
321+
#[repr(C)]
322+
struct mach_timebase_info {
323+
numer: u32,
324+
denom: u32,
325+
}
326+
327+
unsafe extern "C" {
328+
unsafe fn mach_timebase_info(info: *mut mach_timebase_info) -> libc::kern_return_t;
329+
}
330+
331+
let secs = u64::try_from(self.t.tv_sec).ok()?;
332+
333+
let mut timebase = mach_timebase_info { numer: 0, denom: 0 };
334+
assert_eq!(unsafe { mach_timebase_info(&mut timebase) }, libc::KERN_SUCCESS);
335+
336+
// Since `tv_sec` is 64-bit and `tv_nsec` is smaller than 1 billion,
337+
// this cannot overflow. The resulting number needs at most 94 bits.
338+
let nanos =
339+
u128::from(secs) * u128::from(NSEC_PER_SEC) + u128::from(self.t.tv_nsec.as_inner());
340+
// This multiplication cannot overflow since multiplying a 94-bit
341+
// number by a 32-bit number yields a number that needs at most
342+
// 126 bits.
343+
Some((nanos * u128::from(timebase.denom)).div_ceil(u128::from(timebase.numer)))
344+
}
311345
}
312346

313347
impl AsInner<Timespec> for Instant {

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

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -73,6 +73,7 @@ cfg_select! {
7373
target_os = "fuchsia",
7474
target_os = "vxworks",
7575
target_os = "wasi",
76+
target_vendor = "apple",
7677
))]
7778
pub use unix::sleep_until;
7879
#[expect(dead_code)]
@@ -133,6 +134,7 @@ cfg_select! {
133134
target_os = "fuchsia",
134135
target_os = "vxworks",
135136
target_os = "wasi",
137+
target_vendor = "apple",
136138
)))]
137139
pub fn sleep_until(deadline: crate::time::Instant) {
138140
use crate::time::Instant;

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

Lines changed: 43 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -644,6 +644,49 @@ pub fn sleep_until(deadline: crate::time::Instant) {
644644
}
645645
}
646646

647+
#[cfg(target_vendor = "apple")]
648+
pub fn sleep_until(deadline: crate::time::Instant) {
649+
unsafe extern "C" {
650+
// This is defined in the public header mach/mach_time.h alongside
651+
// `mach_absolute_time`, and like it has been available since the very
652+
// beginning.
653+
//
654+
// There isn't really any documentation on this function, except for a
655+
// short reference in technical note 2169:
656+
// https://developer.apple.com/library/archive/technotes/tn2169/_index.html
657+
safe fn mach_wait_until(deadline: u64) -> libc::kern_return_t;
658+
}
659+
660+
// Make sure to round up to ensure that we definitely sleep until after
661+
// the deadline has elapsed.
662+
let Some(deadline) = deadline.into_inner().into_mach_absolute_time_ceil() else {
663+
// Since the deadline is before the system boot time, it has already
664+
// passed, so we can return immediately.
665+
return;
666+
};
667+
668+
// If the deadline is not representable, then sleep for the maximum duration
669+
// possible and worry about the potential clock issues later (in ca. 600 years).
670+
let deadline = deadline.try_into().unwrap_or(u64::MAX);
671+
loop {
672+
match mach_wait_until(deadline) {
673+
// Success! The deadline has passed.
674+
libc::KERN_SUCCESS => break,
675+
// If the sleep gets interrupted by a signal, `mach_wait_until`
676+
// returns KERN_ABORTED, so we need to restart the syscall.
677+
// Also see Apple's implementation of the POSIX `nanosleep`, which
678+
// converts this error to the POSIX equivalent EINTR:
679+
// https://github.com/apple-oss-distributions/Libc/blob/55b54c0a0c37b3b24393b42b90a4c561d6c606b1/gen/nanosleep.c#L281-L306
680+
libc::KERN_ABORTED => continue,
681+
// All other errors indicate that something has gone wrong...
682+
error => {
683+
let description = unsafe { CStr::from_ptr(libc::mach_error_string(error)) };
684+
panic!("mach_wait_until failed: {} (code {error})", description.display())
685+
}
686+
}
687+
}
688+
}
689+
647690
pub fn yield_now() {
648691
let ret = unsafe { libc::sched_yield() };
649692
debug_assert_eq!(ret, 0);

library/std/src/thread/functions.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -318,6 +318,7 @@ pub fn sleep(dur: Duration) {
318318
/// | Hurd | [clock_nanosleep] (Monotonic Clock)] |
319319
/// | Fuchsia | [clock_nanosleep] (Monotonic Clock)] |
320320
/// | Vxworks | [clock_nanosleep] (Monotonic Clock)] |
321+
/// | Apple | `mach_wait_until` |
321322
/// | Other | `sleep_until` uses [`sleep`] and does not issue a syscall itself |
322323
///
323324
/// [currently]: crate::io#platform-specific-behavior

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

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -329,6 +329,30 @@ pub trait EvalContextExt<'tcx>: crate::MiriInterpCxExt<'tcx> {
329329
interp_ok(Scalar::from_i32(0)) // KERN_SUCCESS
330330
}
331331

332+
fn mach_wait_until(&mut self, deadline_op: &OpTy<'tcx>) -> InterpResult<'tcx, Scalar> {
333+
let this = self.eval_context_mut();
334+
335+
this.assert_target_os(Os::MacOs, "mach_wait_until");
336+
337+
let deadline = this.read_scalar(deadline_op)?.to_u64()?;
338+
// Our mach_absolute_time "ticks" are plain nanoseconds.
339+
let duration = Duration::from_nanos(deadline);
340+
341+
this.block_thread(
342+
BlockReason::Sleep,
343+
Some((TimeoutClock::Monotonic, TimeoutAnchor::Absolute, duration)),
344+
callback!(
345+
@capture<'tcx> {}
346+
|_this, unblock: UnblockKind| {
347+
assert_eq!(unblock, UnblockKind::TimedOut);
348+
interp_ok(())
349+
}
350+
)
351+
);
352+
353+
interp_ok(Scalar::from_i32(0)) // KERN_SUCCESS
354+
}
355+
332356
fn nanosleep(&mut self, duration: &OpTy<'tcx>, rem: &OpTy<'tcx>) -> InterpResult<'tcx, Scalar> {
333357
let this = self.eval_context_mut();
334358

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

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -122,6 +122,13 @@ pub trait EvalContextExt<'tcx>: crate::MiriInterpCxExt<'tcx> {
122122
this.write_scalar(result, dest)?;
123123
}
124124

125+
// FIXME: add a test that directly calls this function.
126+
"mach_wait_until" => {
127+
let [deadline] = this.check_shim_sig_lenient(abi, CanonAbi::C, link_name, args)?;
128+
let result = this.mach_wait_until(deadline)?;
129+
this.write_scalar(result, dest)?;
130+
}
131+
125132
// Access to command-line arguments
126133
"_NSGetArgc" => {
127134
// FIXME: This does not have a direct test (#3179).

typos.toml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -35,6 +35,7 @@ targetting = "targetting"
3535
unparseable = "unparseable"
3636
unstability = "unstability"
3737
unstalled = "unstalled"
38+
numer = "numer"
3839

3940
# this can be valid word, depends on dictionary edition
4041
#matcheable = "matcheable"

0 commit comments

Comments
 (0)