std: move thread into sys

This commit is contained in:
joboet 2025-08-09 19:07:08 +02:00
parent 4b15dd5a84
commit ad08577a50
No known key found for this signature in database
GPG key ID: 704E0149B0194B3C
29 changed files with 843 additions and 1043 deletions

View file

@ -31,6 +31,7 @@ pub mod process;
pub mod random;
pub mod stdio;
pub mod sync;
pub mod thread;
pub mod thread_local;
// FIXME(117276): remove this, move feature implementations into individual

View file

@ -25,7 +25,6 @@ pub mod futex;
pub mod os;
#[path = "../unsupported/pipe.rs"]
pub mod pipe;
pub mod thread;
pub mod time;
pub fn unsupported<T>() -> crate::io::Result<T> {

View file

@ -67,7 +67,7 @@ extern "C" fn entry(p1: u64, p2: u64, p3: u64, secondary: bool, p4: u64, p5: u64
let tls_guard = unsafe { tls.activate() };
if secondary {
let join_notifier = super::thread::Thread::entry();
let join_notifier = crate::sys::thread::Thread::entry();
drop(tls_guard);
drop(join_notifier);

View file

@ -13,7 +13,6 @@ mod libunwind_integration;
pub mod os;
#[path = "../unsupported/pipe.rs"]
pub mod pipe;
pub mod thread;
pub mod thread_parking;
pub mod time;
pub mod waitqueue;

View file

@ -10,10 +10,8 @@ pub mod itron {
pub mod error;
pub mod spin;
pub mod task;
pub mod thread;
pub mod thread_parking;
pub mod time;
use super::unsupported;
}
// `error` is `pub(crate)` so that it can be accessed by `itron/error.rs` as
@ -22,7 +20,7 @@ pub(crate) mod error;
pub mod os;
#[path = "../unsupported/pipe.rs"]
pub mod pipe;
pub use self::itron::{thread, thread_parking};
pub use self::itron::thread_parking;
pub mod time;
// SAFETY: must be called only once during runtime initialization.

View file

@ -9,7 +9,6 @@
pub mod os;
#[path = "../unsupported/pipe.rs"]
pub mod pipe;
pub mod thread;
#[allow(non_upper_case_globals)]
#[path = "../unix/time.rs"]
pub mod time;

View file

@ -17,7 +17,6 @@ pub mod helpers;
pub mod os;
#[path = "../unsupported/pipe.rs"]
pub mod pipe;
pub mod thread;
pub mod time;
#[cfg(test)]

View file

@ -17,7 +17,6 @@ pub mod os;
pub mod pipe;
pub mod stack_overflow;
pub mod sync;
pub mod thread;
pub mod thread_parking;
pub mod time;
@ -55,7 +54,7 @@ pub unsafe fn init(argc: isize, argv: *const *const u8, sigpipe: u8) {
// thread-id for the main thread and so renaming the main thread will rename the
// process and we only want to enable this on platforms we've tested.
if cfg!(target_vendor = "apple") {
thread::Thread::set_name(&c"main");
crate::sys::thread::set_name(c"main");
}
unsafe fn sanitize_standard_fds() {

View file

@ -20,7 +20,6 @@ pub mod futex;
pub mod os;
#[path = "../unsupported/pipe.rs"]
pub mod pipe;
pub mod thread;
pub mod time;
#[path = "../unsupported/common.rs"]

View file

@ -14,7 +14,6 @@ pub mod futex;
pub mod os;
#[path = "../unsupported/pipe.rs"]
pub mod pipe;
pub mod thread;
pub mod time;
#[path = "../unsupported/common.rs"]

View file

@ -25,7 +25,7 @@ impl Instant {
Some(Instant(self.0.checked_sub(*other)?))
}
pub(super) fn as_duration(&self) -> &Duration {
pub(crate) fn as_duration(&self) -> &Duration {
&self.0
}
}

View file

@ -23,18 +23,9 @@ pub mod pipe;
#[path = "../unsupported/time.rs"]
pub mod time;
cfg_select! {
target_feature = "atomics" => {
#[path = "atomics/futex.rs"]
pub mod futex;
#[path = "atomics/thread.rs"]
pub mod thread;
}
_ => {
#[path = "../unsupported/thread.rs"]
pub mod thread;
}
}
#[cfg(target_feature = "atomics")]
#[path = "atomics/futex.rs"]
pub mod futex;
#[path = "../unsupported/common.rs"]
#[deny(unsafe_op_in_unsafe_fn)]

View file

@ -20,7 +20,6 @@ pub mod futex;
pub mod handle;
pub mod os;
pub mod pipe;
pub mod thread;
pub mod time;
cfg_select! {
not(target_vendor = "uwp") => {
@ -48,9 +47,9 @@ pub unsafe fn init(_argc: isize, _argv: *const *const u8, _sigpipe: u8) {
unsafe {
stack_overflow::init();
// Normally, `thread::spawn` will call `Thread::set_name` but since this thread already
// Normally, `thread::spawn` will call `set_name` but since this thread already
// exists, we have to call it ourselves.
thread::Thread::set_name_wide(wide_str!("main"));
crate::sys::thread::set_name_wide(wide_str!("main"));
}
}

View file

@ -232,7 +232,7 @@ mod perf_counter {
}
/// A timer you can wait on.
pub(super) struct WaitableTimer {
pub(crate) struct WaitableTimer {
handle: c::HANDLE,
}
impl WaitableTimer {

View file

@ -5,7 +5,6 @@ use crate::os::xous::ffi::exit;
pub mod os;
#[path = "../unsupported/pipe.rs"]
pub mod pipe;
pub mod thread;
pub mod time;
#[path = "../unsupported/common.rs"]

View file

@ -1,10 +1,5 @@
#![allow(dead_code)]
use super::hermit_abi;
use crate::ffi::CStr;
use crate::mem::ManuallyDrop;
use crate::num::NonZero;
use crate::time::{Duration, Instant};
use crate::time::Duration;
use crate::{io, ptr};
pub type Tid = hermit_abi::Tid;
@ -68,57 +63,30 @@ impl Thread {
}
}
#[inline]
pub fn yield_now() {
unsafe {
hermit_abi::yield_now();
}
}
#[inline]
pub fn set_name(_name: &CStr) {
// nope
}
#[inline]
pub fn sleep(dur: Duration) {
let micros = dur.as_micros() + if dur.subsec_nanos() % 1_000 > 0 { 1 } else { 0 };
let micros = u64::try_from(micros).unwrap_or(u64::MAX);
unsafe {
hermit_abi::usleep(micros);
}
}
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);
}
}
#[inline]
pub fn id(&self) -> Tid {
self.tid
}
#[inline]
pub fn into_id(self) -> Tid {
ManuallyDrop::new(self).tid
}
}
pub(crate) fn current_os_id() -> Option<u64> {
None
}
pub fn available_parallelism() -> io::Result<NonZero<usize>> {
unsafe { Ok(NonZero::new_unchecked(hermit_abi::available_parallelism())) }
}
#[inline]
pub fn sleep(dur: Duration) {
let micros = dur.as_micros() + if dur.subsec_nanos() % 1_000 > 0 { 1 } else { 0 };
let micros = u64::try_from(micros).unwrap_or(u64::MAX);
unsafe {
hermit_abi::usleep(micros);
}
}
#[inline]
pub fn yield_now() {
unsafe {
hermit_abi::yield_now();
}
}

View file

@ -0,0 +1,152 @@
cfg_select! {
target_os = "hermit" => {
mod hermit;
pub use hermit::{Thread, available_parallelism, sleep, yield_now, DEFAULT_MIN_STACK_SIZE};
#[expect(dead_code)]
mod unsupported;
pub use unsupported::{current_os_id, set_name};
}
all(target_vendor = "fortanix", target_env = "sgx") => {
mod sgx;
pub use sgx::{Thread, current_os_id, sleep, yield_now, DEFAULT_MIN_STACK_SIZE};
// SGX should protect in-enclave data from outside attackers, so there
// must not be any data leakage to the OS, particularly no 1-1 mapping
// between SGX thread names and OS thread names. Hence `set_name` is
// intentionally a no-op.
//
// Note that the internally visible SGX thread name is already provided
// by the platform-agnostic Rust thread code. This can be observed in
// the [`std::thread::tests::test_named_thread`] test, which succeeds
// as-is with the SGX target.
#[expect(dead_code)]
mod unsupported;
pub use unsupported::{available_parallelism, set_name};
}
target_os = "solid_asp3" => {
mod solid;
pub use solid::{Thread, sleep, yield_now, DEFAULT_MIN_STACK_SIZE};
#[expect(dead_code)]
mod unsupported;
pub use unsupported::{available_parallelism, current_os_id, set_name};
}
target_os = "teeos" => {
mod teeos;
pub use teeos::{Thread, sleep, yield_now, DEFAULT_MIN_STACK_SIZE};
#[expect(dead_code)]
mod unsupported;
pub use unsupported::{available_parallelism, current_os_id, set_name};
}
target_os = "uefi" => {
mod uefi;
pub use uefi::{available_parallelism, sleep};
#[expect(dead_code)]
mod unsupported;
pub use unsupported::{Thread, current_os_id, set_name, yield_now, DEFAULT_MIN_STACK_SIZE};
}
target_family = "unix" => {
mod unix;
pub use unix::{Thread, available_parallelism, current_os_id, sleep, yield_now, DEFAULT_MIN_STACK_SIZE};
#[cfg(not(any(
target_env = "newlib",
target_os = "l4re",
target_os = "emscripten",
target_os = "redox",
target_os = "hurd",
target_os = "aix",
)))]
pub use unix::set_name;
#[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 use unix::sleep_until;
#[expect(dead_code)]
mod unsupported;
#[cfg(any(
target_env = "newlib",
target_os = "l4re",
target_os = "emscripten",
target_os = "redox",
target_os = "hurd",
target_os = "aix",
))]
pub use unsupported::set_name;
}
all(target_os = "wasi", target_env = "p1") => {
mod wasip1;
pub use wasip1::{DEFAULT_MIN_STACK_SIZE, sleep, yield_now};
#[cfg(target_feature = "atomics")]
pub use wasip1::{Thread, available_parallelism};
#[expect(dead_code)]
mod unsupported;
pub use unsupported::{current_os_id, set_name};
#[cfg(not(target_feature = "atomics"))]
pub use unsupported::{Thread, available_parallelism};
}
all(target_os = "wasi", target_env = "p2") => {
mod wasip2;
pub use wasip2::{sleep, sleep_until};
#[expect(dead_code)]
mod unsupported;
// Note that unlike WASIp1 even if the wasm `atomics` feature is enabled
// there is no support for threads, not even experimentally, not even in
// wasi-libc. Thus this is unconditionally unsupported.
pub use unsupported::{Thread, available_parallelism, current_os_id, set_name, yield_now, DEFAULT_MIN_STACK_SIZE};
}
all(target_family = "wasm", target_feature = "atomics") => {
mod wasm;
pub use wasm::sleep;
#[expect(dead_code)]
mod unsupported;
pub use unsupported::{Thread, available_parallelism, current_os_id, set_name, yield_now, DEFAULT_MIN_STACK_SIZE};
}
target_os = "windows" => {
mod windows;
pub use windows::{Thread, available_parallelism, current_os_id, set_name, set_name_wide, sleep, yield_now, DEFAULT_MIN_STACK_SIZE};
}
target_os = "xous" => {
mod xous;
pub use xous::{Thread, available_parallelism, sleep, yield_now, DEFAULT_MIN_STACK_SIZE};
#[expect(dead_code)]
mod unsupported;
pub use unsupported::{current_os_id, set_name};
}
_ => {
mod unsupported;
pub use unsupported::{Thread, available_parallelism, current_os_id, set_name, sleep, yield_now, DEFAULT_MIN_STACK_SIZE};
}
}
#[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",
all(target_os = "wasi", target_env = "p2"),
)))]
pub fn sleep_until(deadline: crate::time::Instant) {
use crate::time::Instant;
let now = Instant::now();
if let Some(delay) = deadline.checked_duration_since(now) {
sleep(delay);
}
}

View file

@ -1,11 +1,8 @@
#![cfg_attr(test, allow(dead_code))] // why is this necessary?
use super::abi::{thread, usercalls};
use super::unsupported;
use crate::ffi::CStr;
use crate::io;
use crate::num::NonZero;
use crate::time::{Duration, Instant};
use crate::sys::pal::abi::{thread, usercalls};
use crate::time::Duration;
pub struct Thread(task_queue::JoinHandle);
@ -108,51 +105,27 @@ impl Thread {
Ok(Thread(handle))
}
pub(super) fn entry() -> JoinNotifier {
pub(crate) fn entry() -> JoinNotifier {
let mut pending_tasks = task_queue::lock();
let task = rtunwrap!(Some, pending_tasks.pop());
drop(pending_tasks); // make sure to not hold the task queue lock longer than necessary
task.run()
}
pub fn yield_now() {
let wait_error = rtunwrap!(Err, usercalls::wait(0, usercalls::raw::WAIT_NO));
rtassert!(wait_error.kind() == io::ErrorKind::WouldBlock);
}
/// SGX should protect in-enclave data from the outside (attacker),
/// so there should be no data leakage to the OS,
/// and therefore also no 1-1 mapping between SGX thread names and OS thread names.
///
/// This is why the method is intentionally No-Op.
pub fn set_name(_name: &CStr) {
// Note that the internally visible SGX thread name is already provided
// by the platform-agnostic (target-agnostic) Rust thread code.
// This can be observed in the [`std::thread::tests::test_named_thread`] test,
// which succeeds as-is with the SGX target.
}
pub fn sleep(dur: Duration) {
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();
}
}
pub(crate) fn current_os_id() -> Option<u64> {
pub fn current_os_id() -> Option<u64> {
Some(thread::current().addr().get() as u64)
}
pub fn available_parallelism() -> io::Result<NonZero<usize>> {
unsupported()
pub fn sleep(dur: Duration) {
usercalls::wait_timeout(0, dur, || true);
}
pub fn yield_now() {
let wait_error = rtunwrap!(Err, usercalls::wait(0, usercalls::raw::WAIT_NO));
rtassert!(wait_error.kind() == io::ErrorKind::WouldBlock);
}

View file

@ -1,16 +1,14 @@
//! Thread implementation backed by μITRON tasks. Assumes `acre_tsk` and
//! `exd_tsk` are available.
use super::error::{ItronError, expect_success, expect_success_aborting};
use super::time::dur2reltims;
use super::{abi, task};
use crate::cell::UnsafeCell;
use crate::ffi::CStr;
use crate::mem::ManuallyDrop;
use crate::num::NonZero;
use crate::ptr::NonNull;
use crate::sync::atomic::{Atomic, AtomicUsize, Ordering};
use crate::time::{Duration, Instant};
use crate::sys::pal::itron::error::{ItronError, expect_success, expect_success_aborting};
use crate::sys::pal::itron::time::dur2reltims;
use crate::sys::pal::itron::{abi, task};
use crate::time::Duration;
use crate::{hint, io};
pub struct Thread {
@ -195,28 +193,6 @@ impl Thread {
Ok(Self { p_inner, task: new_task })
}
pub fn yield_now() {
expect_success(unsafe { abi::rot_rdq(abi::TPRI_SELF) }, &"rot_rdq");
}
pub fn set_name(_name: &CStr) {
// nope
}
pub fn sleep(dur: Duration) {
for timeout in dur2reltims(dur) {
expect_success(unsafe { abi::dly_tsk(timeout) }, &"dly_tsk");
}
}
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() };
@ -361,10 +337,12 @@ unsafe fn terminate_and_delete_current_task() -> ! {
unsafe { crate::hint::unreachable_unchecked() };
}
pub(crate) fn current_os_id() -> Option<u64> {
None
pub fn yield_now() {
expect_success(unsafe { abi::rot_rdq(abi::TPRI_SELF) }, &"rot_rdq");
}
pub fn available_parallelism() -> io::Result<NonZero<usize>> {
super::unsupported()
pub fn sleep(dur: Duration) {
for timeout in dur2reltims(dur) {
expect_success(unsafe { abi::dly_tsk(timeout) }, &"dly_tsk");
}
}

View file

@ -1,12 +1,18 @@
use crate::ffi::CStr;
use crate::mem::{self, ManuallyDrop};
use crate::num::NonZero;
use crate::sys::os;
use crate::time::{Duration, Instant};
use crate::time::Duration;
use crate::{cmp, io, ptr};
pub const DEFAULT_MIN_STACK_SIZE: usize = 8 * 1024;
unsafe extern "C" {
safe fn TEE_Wait(timeout: u32) -> u32;
}
fn min_stack_size(_: *const libc::pthread_attr_t) -> usize {
libc::PTHREAD_STACK_MIN.try_into().expect("Infallible")
}
pub struct Thread {
id: libc::pthread_t,
}
@ -16,10 +22,6 @@ pub struct Thread {
unsafe impl Send for Thread {}
unsafe impl Sync for Thread {}
unsafe extern "C" {
pub fn TEE_Wait(timeout: u32) -> u32;
}
impl Thread {
// unsafe: see thread::Builder::spawn_unchecked for safety requirements
pub unsafe fn new(
@ -74,7 +76,7 @@ impl Thread {
} else {
// The new thread will start running earliest after the next yield.
// We add a yield here, so that the user does not have to.
Thread::yield_now();
yield_now();
Ok(Thread { id: native })
};
@ -91,36 +93,6 @@ impl Thread {
}
}
pub fn yield_now() {
let ret = unsafe { libc::sched_yield() };
debug_assert_eq!(ret, 0);
}
/// This does not do anything on teeos
pub fn set_name(_name: &CStr) {
// Both pthread_setname_np and prctl are not available to the TA,
// so we can't implement this currently. If the need arises please
// contact the teeos rustzone team.
}
/// only main thread could wait for sometime in teeos
pub fn sleep(dur: Duration) {
let sleep_millis = dur.as_millis();
let final_sleep: u32 =
if sleep_millis >= u32::MAX as u128 { u32::MAX } else { sleep_millis as u32 };
unsafe {
let _ = TEE_Wait(final_sleep);
}
}
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();
@ -128,10 +100,6 @@ impl Thread {
assert!(ret == 0, "failed to join thread: {}", io::Error::from_raw_os_error(ret));
}
pub fn id(&self) -> libc::pthread_t {
self.id
}
pub fn into_id(self) -> libc::pthread_t {
ManuallyDrop::new(self).id
}
@ -144,16 +112,15 @@ impl Drop for Thread {
}
}
pub(crate) fn current_os_id() -> Option<u64> {
None
pub fn yield_now() {
let ret = unsafe { libc::sched_yield() };
debug_assert_eq!(ret, 0);
}
// Note: Both `sched_getaffinity` and `sysconf` are available but not functional on
// teeos, so this function always returns an Error!
pub fn available_parallelism() -> io::Result<NonZero<usize>> {
Err(io::Error::UNKNOWN_THREAD_COUNT)
}
fn min_stack_size(_: *const libc::pthread_attr_t) -> usize {
libc::PTHREAD_STACK_MIN.try_into().expect("Infallible")
/// only main thread could wait for sometime in teeos
pub fn sleep(dur: Duration) {
let sleep_millis = dur.as_millis();
let final_sleep: u32 =
if sleep_millis >= u32::MAX as u128 { u32::MAX } else { sleep_millis as u32 };
TEE_Wait(final_sleep);
}

View file

@ -1,66 +1,25 @@
use super::unsupported;
use crate::ffi::CStr;
use crate::io;
use crate::num::NonZero;
use crate::ptr::NonNull;
use crate::time::{Duration, Instant};
pub struct Thread(!);
pub const DEFAULT_MIN_STACK_SIZE: usize = 64 * 1024;
impl Thread {
// unsafe: see thread::Builder::spawn_unchecked for safety requirements
pub unsafe fn new(
_stack: usize,
_name: Option<&str>,
_p: Box<dyn FnOnce()>,
) -> io::Result<Thread> {
unsupported()
}
pub fn yield_now() {
// do nothing
}
pub fn set_name(_name: &CStr) {
// nope
}
pub fn sleep(dur: Duration) {
let boot_services: NonNull<r_efi::efi::BootServices> =
crate::os::uefi::env::boot_services().expect("can't sleep").cast();
let mut dur_ms = dur.as_micros();
// ceil up to the nearest microsecond
if dur.subsec_nanos() % 1000 > 0 {
dur_ms += 1;
}
while dur_ms > 0 {
let ms = crate::cmp::min(dur_ms, usize::MAX as u128);
let _ = unsafe { ((*boot_services.as_ptr()).stall)(ms as usize) };
dur_ms -= ms;
}
}
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
}
}
pub(crate) fn current_os_id() -> Option<u64> {
None
}
use crate::time::Duration;
pub fn available_parallelism() -> io::Result<NonZero<usize>> {
// UEFI is single threaded
Ok(NonZero::new(1).unwrap())
}
pub fn sleep(dur: Duration) {
let boot_services: NonNull<r_efi::efi::BootServices> =
crate::os::uefi::env::boot_services().expect("can't sleep").cast();
let mut dur_ms = dur.as_micros();
// ceil up to the nearest microsecond
if dur.subsec_nanos() % 1000 > 0 {
dur_ms += 1;
}
while dur_ms > 0 {
let ms = crate::cmp::min(dur_ms, usize::MAX as u128);
let _ = unsafe { ((*boot_services.as_ptr()).stall)(ms as usize) };
dur_ms -= ms;
}
}

View file

@ -1,3 +1,11 @@
#[cfg(not(any(
target_env = "newlib",
target_os = "l4re",
target_os = "emscripten",
target_os = "redox",
target_os = "hurd",
target_os = "aix",
)))]
use crate::ffi::CStr;
use crate::mem::{self, ManuallyDrop};
use crate::num::NonZero;
@ -6,7 +14,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, Instant};
use crate::time::Duration;
use crate::{cmp, io, ptr};
#[cfg(not(any(
target_os = "l4re",
@ -121,273 +129,6 @@ impl Thread {
}
}
pub fn yield_now() {
let ret = unsafe { libc::sched_yield() };
debug_assert_eq!(ret, 0);
}
#[cfg(target_os = "android")]
pub fn set_name(name: &CStr) {
const PR_SET_NAME: libc::c_int = 15;
unsafe {
let res = libc::prctl(
PR_SET_NAME,
name.as_ptr(),
0 as libc::c_ulong,
0 as libc::c_ulong,
0 as libc::c_ulong,
);
// We have no good way of propagating errors here, but in debug-builds let's check that this actually worked.
debug_assert_eq!(res, 0);
}
}
#[cfg(any(
target_os = "linux",
target_os = "freebsd",
target_os = "dragonfly",
target_os = "nuttx",
target_os = "cygwin"
))]
pub fn set_name(name: &CStr) {
unsafe {
cfg_select! {
any(target_os = "linux", target_os = "cygwin") => {
// Linux and Cygwin limits the allowed length of the name.
const TASK_COMM_LEN: usize = 16;
let name = truncate_cstr::<{ TASK_COMM_LEN }>(name);
}
_ => {
// FreeBSD, DragonFly BSD and NuttX do not enforce length limits.
}
};
// Available since glibc 2.12, musl 1.1.16, and uClibc 1.0.20 for Linux,
// FreeBSD 12.2 and 13.0, and DragonFly BSD 6.0.
let res = libc::pthread_setname_np(libc::pthread_self(), name.as_ptr());
// We have no good way of propagating errors here, but in debug-builds let's check that this actually worked.
debug_assert_eq!(res, 0);
}
}
#[cfg(target_os = "openbsd")]
pub fn set_name(name: &CStr) {
unsafe {
libc::pthread_set_name_np(libc::pthread_self(), name.as_ptr());
}
}
#[cfg(target_vendor = "apple")]
pub fn set_name(name: &CStr) {
unsafe {
let name = truncate_cstr::<{ libc::MAXTHREADNAMESIZE }>(name);
let res = libc::pthread_setname_np(name.as_ptr());
// We have no good way of propagating errors here, but in debug-builds let's check that this actually worked.
debug_assert_eq!(res, 0);
}
}
#[cfg(target_os = "netbsd")]
pub fn set_name(name: &CStr) {
unsafe {
let res = libc::pthread_setname_np(
libc::pthread_self(),
c"%s".as_ptr(),
name.as_ptr() as *mut libc::c_void,
);
debug_assert_eq!(res, 0);
}
}
#[cfg(any(target_os = "solaris", target_os = "illumos", target_os = "nto"))]
pub fn set_name(name: &CStr) {
weak!(
fn pthread_setname_np(
thread: libc::pthread_t,
name: *const libc::c_char,
) -> libc::c_int;
);
if let Some(f) = pthread_setname_np.get() {
#[cfg(target_os = "nto")]
const THREAD_NAME_MAX: usize = libc::_NTO_THREAD_NAME_MAX as usize;
#[cfg(any(target_os = "solaris", target_os = "illumos"))]
const THREAD_NAME_MAX: usize = 32;
let name = truncate_cstr::<{ THREAD_NAME_MAX }>(name);
let res = unsafe { f(libc::pthread_self(), name.as_ptr()) };
debug_assert_eq!(res, 0);
}
}
#[cfg(target_os = "fuchsia")]
pub fn set_name(name: &CStr) {
use super::fuchsia::*;
unsafe {
zx_object_set_property(
zx_thread_self(),
ZX_PROP_NAME,
name.as_ptr() as *const libc::c_void,
name.to_bytes().len(),
);
}
}
#[cfg(target_os = "haiku")]
pub fn set_name(name: &CStr) {
unsafe {
let thread_self = libc::find_thread(ptr::null_mut());
let res = libc::rename_thread(thread_self, name.as_ptr());
// We have no good way of propagating errors here, but in debug-builds let's check that this actually worked.
debug_assert_eq!(res, libc::B_OK);
}
}
#[cfg(target_os = "vxworks")]
pub fn set_name(name: &CStr) {
let mut name = truncate_cstr::<{ (libc::VX_TASK_RENAME_LENGTH - 1) as usize }>(name);
let res = unsafe { libc::taskNameSet(libc::taskIdSelf(), name.as_mut_ptr()) };
debug_assert_eq!(res, libc::OK);
}
#[cfg(any(
target_env = "newlib",
target_os = "l4re",
target_os = "emscripten",
target_os = "redox",
target_os = "hurd",
target_os = "aix",
))]
pub fn set_name(_name: &CStr) {
// Newlib and Emscripten have no way to set a thread name.
}
#[cfg(not(target_os = "espidf"))]
pub fn sleep(dur: Duration) {
let mut secs = dur.as_secs();
let mut nsecs = dur.subsec_nanos() as _;
// If we're awoken with a signal then the return value will be -1 and
// nanosleep will fill in `ts` with the remaining time.
unsafe {
while secs > 0 || nsecs > 0 {
let mut ts = libc::timespec {
tv_sec: cmp::min(libc::time_t::MAX as u64, secs) as libc::time_t,
tv_nsec: nsecs,
};
secs -= ts.tv_sec as u64;
let ts_ptr = &raw mut ts;
if libc::nanosleep(ts_ptr, ts_ptr) == -1 {
assert_eq!(os::errno(), libc::EINTR);
secs += ts.tv_sec as u64;
nsecs = ts.tv_nsec;
} else {
nsecs = 0;
}
}
}
}
#[cfg(target_os = "espidf")]
pub fn sleep(dur: Duration) {
// ESP-IDF does not have `nanosleep`, so we use `usleep` instead.
// As per the documentation of `usleep`, it is expected to support
// sleep times as big as at least up to 1 second.
//
// ESP-IDF does support almost up to `u32::MAX`, but due to a potential integer overflow in its
// `usleep` implementation
// (https://github.com/espressif/esp-idf/blob/d7ca8b94c852052e3bc33292287ef4dd62c9eeb1/components/newlib/time.c#L210),
// we limit the sleep time to the maximum one that would not cause the underlying `usleep` implementation to overflow
// (`portTICK_PERIOD_MS` can be anything between 1 to 1000, and is 10 by default).
const MAX_MICROS: u32 = u32::MAX - 1_000_000 - 1;
// Add any nanoseconds smaller than a microsecond as an extra microsecond
// so as to comply with the `std::thread::sleep` contract which mandates
// implementations to sleep for _at least_ the provided `dur`.
// We can't overflow `micros` as it is a `u128`, while `Duration` is a pair of
// (`u64` secs, `u32` nanos), where the nanos are strictly smaller than 1 second
// (i.e. < 1_000_000_000)
let mut micros = dur.as_micros() + if dur.subsec_nanos() % 1_000 > 0 { 1 } else { 0 };
while micros > 0 {
let st = if micros > MAX_MICROS as u128 { MAX_MICROS } else { micros as u32 };
unsafe {
libc::usleep(st);
}
micros -= st as u128;
}
}
// Any unix that has clock_nanosleep
// If this list changes update the MIRI chock_nanosleep shim
#[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 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.
let now = Instant::now();
if let Some(delay) = deadline.checked_duration_since(now) {
Self::sleep(delay);
}
return;
};
unsafe {
// 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"
);
}
}
}
}
// 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()) };
@ -410,84 +151,6 @@ impl Drop for Thread {
}
}
pub(crate) fn current_os_id() -> Option<u64> {
// Most Unix platforms have a way to query an integer ID of the current thread, all with
// slightly different spellings.
//
// The OS thread ID is used rather than `pthread_self` so as to match what will be displayed
// for process inspection (debuggers, trace, `top`, etc.).
cfg_select! {
// Most platforms have a function returning a `pid_t` or int, which is an `i32`.
any(target_os = "android", target_os = "linux") => {
use crate::sys::weak::syscall;
// `libc::gettid` is only available on glibc 2.30+, but the syscall is available
// since Linux 2.4.11.
syscall!(fn gettid() -> libc::pid_t;);
// SAFETY: FFI call with no preconditions.
let id: libc::pid_t = unsafe { gettid() };
Some(id as u64)
}
target_os = "nto" => {
// SAFETY: FFI call with no preconditions.
let id: libc::pid_t = unsafe { libc::gettid() };
Some(id as u64)
}
target_os = "openbsd" => {
// SAFETY: FFI call with no preconditions.
let id: libc::pid_t = unsafe { libc::getthrid() };
Some(id as u64)
}
target_os = "freebsd" => {
// SAFETY: FFI call with no preconditions.
let id: libc::c_int = unsafe { libc::pthread_getthreadid_np() };
Some(id as u64)
}
target_os = "netbsd" => {
// SAFETY: FFI call with no preconditions.
let id: libc::lwpid_t = unsafe { libc::_lwp_self() };
Some(id as u64)
}
any(target_os = "illumos", target_os = "solaris") => {
// On Illumos and Solaris, the `pthread_t` is the same as the OS thread ID.
// SAFETY: FFI call with no preconditions.
let id: libc::pthread_t = unsafe { libc::pthread_self() };
Some(id as u64)
}
target_vendor = "apple" => {
// Apple allows querying arbitrary thread IDs, `thread=NULL` queries the current thread.
let mut id = 0u64;
// SAFETY: `thread_id` is a valid pointer, no other preconditions.
let status: libc::c_int = unsafe { libc::pthread_threadid_np(0, &mut id) };
if status == 0 {
Some(id)
} else {
None
}
}
// Other platforms don't have an OS thread ID or don't have a way to access it.
_ => None,
}
}
#[cfg(any(
target_os = "linux",
target_os = "nto",
target_os = "solaris",
target_os = "illumos",
target_os = "vxworks",
target_os = "cygwin",
target_vendor = "apple",
))]
fn truncate_cstr<const MAX_WITH_NUL: usize>(cstr: &CStr) -> [libc::c_char; MAX_WITH_NUL] {
let mut result = [0; MAX_WITH_NUL];
for (src, dst) in cstr.to_bytes().iter().zip(&mut result[..MAX_WITH_NUL - 1]) {
*dst = *src as libc::c_char;
}
result
}
pub fn available_parallelism() -> io::Result<NonZero<usize>> {
cfg_select! {
any(
@ -668,6 +331,318 @@ pub fn available_parallelism() -> io::Result<NonZero<usize>> {
}
}
pub fn current_os_id() -> Option<u64> {
// Most Unix platforms have a way to query an integer ID of the current thread, all with
// slightly different spellings.
//
// The OS thread ID is used rather than `pthread_self` so as to match what will be displayed
// for process inspection (debuggers, trace, `top`, etc.).
cfg_select! {
// Most platforms have a function returning a `pid_t` or int, which is an `i32`.
any(target_os = "android", target_os = "linux") => {
use crate::sys::pal::weak::syscall;
// `libc::gettid` is only available on glibc 2.30+, but the syscall is available
// since Linux 2.4.11.
syscall!(fn gettid() -> libc::pid_t;);
// SAFETY: FFI call with no preconditions.
let id: libc::pid_t = unsafe { gettid() };
Some(id as u64)
}
target_os = "nto" => {
// SAFETY: FFI call with no preconditions.
let id: libc::pid_t = unsafe { libc::gettid() };
Some(id as u64)
}
target_os = "openbsd" => {
// SAFETY: FFI call with no preconditions.
let id: libc::pid_t = unsafe { libc::getthrid() };
Some(id as u64)
}
target_os = "freebsd" => {
// SAFETY: FFI call with no preconditions.
let id: libc::c_int = unsafe { libc::pthread_getthreadid_np() };
Some(id as u64)
}
target_os = "netbsd" => {
// SAFETY: FFI call with no preconditions.
let id: libc::lwpid_t = unsafe { libc::_lwp_self() };
Some(id as u64)
}
any(target_os = "illumos", target_os = "solaris") => {
// On Illumos and Solaris, the `pthread_t` is the same as the OS thread ID.
// SAFETY: FFI call with no preconditions.
let id: libc::pthread_t = unsafe { libc::pthread_self() };
Some(id as u64)
}
target_vendor = "apple" => {
// Apple allows querying arbitrary thread IDs, `thread=NULL` queries the current thread.
let mut id = 0u64;
// SAFETY: `thread_id` is a valid pointer, no other preconditions.
let status: libc::c_int = unsafe { libc::pthread_threadid_np(0, &mut id) };
if status == 0 {
Some(id)
} else {
None
}
}
// Other platforms don't have an OS thread ID or don't have a way to access it.
_ => None,
}
}
#[cfg(any(
target_os = "linux",
target_os = "nto",
target_os = "solaris",
target_os = "illumos",
target_os = "vxworks",
target_os = "cygwin",
target_vendor = "apple",
))]
fn truncate_cstr<const MAX_WITH_NUL: usize>(cstr: &CStr) -> [libc::c_char; MAX_WITH_NUL] {
let mut result = [0; MAX_WITH_NUL];
for (src, dst) in cstr.to_bytes().iter().zip(&mut result[..MAX_WITH_NUL - 1]) {
*dst = *src as libc::c_char;
}
result
}
#[cfg(target_os = "android")]
pub fn set_name(name: &CStr) {
const PR_SET_NAME: libc::c_int = 15;
unsafe {
let res = libc::prctl(
PR_SET_NAME,
name.as_ptr(),
0 as libc::c_ulong,
0 as libc::c_ulong,
0 as libc::c_ulong,
);
// We have no good way of propagating errors here, but in debug-builds let's check that this actually worked.
debug_assert_eq!(res, 0);
}
}
#[cfg(any(
target_os = "linux",
target_os = "freebsd",
target_os = "dragonfly",
target_os = "nuttx",
target_os = "cygwin"
))]
pub fn set_name(name: &CStr) {
unsafe {
cfg_select! {
any(target_os = "linux", target_os = "cygwin") => {
// Linux and Cygwin limits the allowed length of the name.
const TASK_COMM_LEN: usize = 16;
let name = truncate_cstr::<{ TASK_COMM_LEN }>(name);
}
_ => {
// FreeBSD, DragonFly BSD and NuttX do not enforce length limits.
}
};
// Available since glibc 2.12, musl 1.1.16, and uClibc 1.0.20 for Linux,
// FreeBSD 12.2 and 13.0, and DragonFly BSD 6.0.
let res = libc::pthread_setname_np(libc::pthread_self(), name.as_ptr());
// We have no good way of propagating errors here, but in debug-builds let's check that this actually worked.
debug_assert_eq!(res, 0);
}
}
#[cfg(target_os = "openbsd")]
pub fn set_name(name: &CStr) {
unsafe {
libc::pthread_set_name_np(libc::pthread_self(), name.as_ptr());
}
}
#[cfg(target_vendor = "apple")]
pub fn set_name(name: &CStr) {
unsafe {
let name = truncate_cstr::<{ libc::MAXTHREADNAMESIZE }>(name);
let res = libc::pthread_setname_np(name.as_ptr());
// We have no good way of propagating errors here, but in debug-builds let's check that this actually worked.
debug_assert_eq!(res, 0);
}
}
#[cfg(target_os = "netbsd")]
pub fn set_name(name: &CStr) {
unsafe {
let res = libc::pthread_setname_np(
libc::pthread_self(),
c"%s".as_ptr(),
name.as_ptr() as *mut libc::c_void,
);
debug_assert_eq!(res, 0);
}
}
#[cfg(any(target_os = "solaris", target_os = "illumos", target_os = "nto"))]
pub fn set_name(name: &CStr) {
weak!(
fn pthread_setname_np(thread: libc::pthread_t, name: *const libc::c_char) -> libc::c_int;
);
if let Some(f) = pthread_setname_np.get() {
#[cfg(target_os = "nto")]
const THREAD_NAME_MAX: usize = libc::_NTO_THREAD_NAME_MAX as usize;
#[cfg(any(target_os = "solaris", target_os = "illumos"))]
const THREAD_NAME_MAX: usize = 32;
let name = truncate_cstr::<{ THREAD_NAME_MAX }>(name);
let res = unsafe { f(libc::pthread_self(), name.as_ptr()) };
debug_assert_eq!(res, 0);
}
}
#[cfg(target_os = "fuchsia")]
pub fn set_name(name: &CStr) {
use crate::sys::pal::fuchsia::*;
unsafe {
zx_object_set_property(
zx_thread_self(),
ZX_PROP_NAME,
name.as_ptr() as *const libc::c_void,
name.to_bytes().len(),
);
}
}
#[cfg(target_os = "haiku")]
pub fn set_name(name: &CStr) {
unsafe {
let thread_self = libc::find_thread(ptr::null_mut());
let res = libc::rename_thread(thread_self, name.as_ptr());
// We have no good way of propagating errors here, but in debug-builds let's check that this actually worked.
debug_assert_eq!(res, libc::B_OK);
}
}
#[cfg(target_os = "vxworks")]
pub fn set_name(name: &CStr) {
let mut name = truncate_cstr::<{ (libc::VX_TASK_RENAME_LENGTH - 1) as usize }>(name);
let res = unsafe { libc::taskNameSet(libc::taskIdSelf(), name.as_mut_ptr()) };
debug_assert_eq!(res, libc::OK);
}
#[cfg(not(target_os = "espidf"))]
pub fn sleep(dur: Duration) {
let mut secs = dur.as_secs();
let mut nsecs = dur.subsec_nanos() as _;
// If we're awoken with a signal then the return value will be -1 and
// nanosleep will fill in `ts` with the remaining time.
unsafe {
while secs > 0 || nsecs > 0 {
let mut ts = libc::timespec {
tv_sec: cmp::min(libc::time_t::MAX as u64, secs) as libc::time_t,
tv_nsec: nsecs,
};
secs -= ts.tv_sec as u64;
let ts_ptr = &raw mut ts;
if libc::nanosleep(ts_ptr, ts_ptr) == -1 {
assert_eq!(os::errno(), libc::EINTR);
secs += ts.tv_sec as u64;
nsecs = ts.tv_nsec;
} else {
nsecs = 0;
}
}
}
}
#[cfg(target_os = "espidf")]
pub fn sleep(dur: Duration) {
// ESP-IDF does not have `nanosleep`, so we use `usleep` instead.
// As per the documentation of `usleep`, it is expected to support
// sleep times as big as at least up to 1 second.
//
// ESP-IDF does support almost up to `u32::MAX`, but due to a potential integer overflow in its
// `usleep` implementation
// (https://github.com/espressif/esp-idf/blob/d7ca8b94c852052e3bc33292287ef4dd62c9eeb1/components/newlib/time.c#L210),
// we limit the sleep time to the maximum one that would not cause the underlying `usleep` implementation to overflow
// (`portTICK_PERIOD_MS` can be anything between 1 to 1000, and is 10 by default).
const MAX_MICROS: u32 = u32::MAX - 1_000_000 - 1;
// Add any nanoseconds smaller than a microsecond as an extra microsecond
// so as to comply with the `std::thread::sleep` contract which mandates
// implementations to sleep for _at least_ the provided `dur`.
// We can't overflow `micros` as it is a `u128`, while `Duration` is a pair of
// (`u64` secs, `u32` nanos), where the nanos are strictly smaller than 1 second
// (i.e. < 1_000_000_000)
let mut micros = dur.as_micros() + if dur.subsec_nanos() % 1_000 > 0 { 1 } else { 0 };
while micros > 0 {
let st = if micros > MAX_MICROS as u128 { MAX_MICROS } else { micros as u32 };
unsafe {
libc::usleep(st);
}
micros -= st as u128;
}
}
// Any unix that has clock_nanosleep
// If this list changes update the MIRI chock_nanosleep shim
#[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: crate::time::Instant) {
use crate::time::Instant;
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.
let now = Instant::now();
if let Some(delay) = deadline.checked_duration_since(now) {
sleep(delay);
}
return;
};
unsafe {
// When we get interrupted (res = EINTR) call clock_nanosleep again
loop {
let res = libc::clock_nanosleep(
crate::sys::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"
);
}
}
}
}
pub fn yield_now() {
let ret = unsafe { libc::sched_yield() };
debug_assert_eq!(ret, 0);
}
#[cfg(any(target_os = "android", target_os = "linux"))]
mod cgroups {
//! Currently not covered

View file

@ -1,8 +1,7 @@
use super::unsupported;
use crate::ffi::CStr;
use crate::io;
use crate::num::NonZero;
use crate::time::{Duration, Instant};
use crate::time::Duration;
pub struct Thread(!);
@ -15,23 +14,7 @@ impl Thread {
_name: Option<&str>,
_p: Box<dyn FnOnce()>,
) -> io::Result<Thread> {
unsupported()
}
pub fn yield_now() {
// do nothing
}
pub fn set_name(_name: &CStr) {
// nope
}
pub fn sleep(_dur: Duration) {
panic!("can't sleep");
}
pub fn sleep_until(_deadline: Instant) {
panic!("can't sleep");
Err(io::Error::UNSUPPORTED_PLATFORM)
}
pub fn join(self) {
@ -39,10 +22,22 @@ impl Thread {
}
}
pub(crate) fn current_os_id() -> Option<u64> {
pub fn available_parallelism() -> io::Result<NonZero<usize>> {
Err(io::Error::UNKNOWN_THREAD_COUNT)
}
pub fn current_os_id() -> Option<u64> {
None
}
pub fn available_parallelism() -> io::Result<NonZero<usize>> {
unsupported()
pub fn yield_now() {
// do nothing
}
pub fn set_name(_name: &CStr) {
// nope
}
pub fn sleep(_dur: Duration) {
panic!("can't sleep");
}

View file

@ -1,214 +1,185 @@
#![forbid(unsafe_op_in_unsafe_fn)]
use crate::ffi::CStr;
#[cfg(target_feature = "atomics")]
use crate::io;
use crate::mem;
#[cfg(target_feature = "atomics")]
use crate::num::NonZero;
use crate::time::{Duration, Instant};
use crate::{io, mem};
#[cfg(target_feature = "atomics")]
use crate::sys::os;
use crate::time::Duration;
#[cfg(target_feature = "atomics")]
use crate::{cmp, ptr};
cfg_select! {
target_feature = "atomics" => {
use crate::cmp;
use crate::ptr;
use crate::sys::os;
// Add a few symbols not in upstream `libc` just yet.
mod libc {
pub use crate::ffi;
pub use libc::*;
// Add a few symbols not in upstream `libc` just yet.
#[cfg(target_feature = "atomics")]
mod libc {
pub use libc::*;
// defined in wasi-libc
// https://github.com/WebAssembly/wasi-libc/blob/a6f871343313220b76009827ed0153586361c0d5/libc-top-half/musl/include/alltypes.h.in#L108
#[repr(C)]
union pthread_attr_union {
__i: [ffi::c_int; if size_of::<ffi::c_long>() == 8 { 14 } else { 9 }],
__vi: [ffi::c_int; if size_of::<ffi::c_long>() == 8 { 14 } else { 9 }],
__s: [ffi::c_ulong; if size_of::<ffi::c_long>() == 8 { 7 } else { 9 }],
}
pub use crate::ffi;
#[repr(C)]
pub struct pthread_attr_t {
__u: pthread_attr_union,
}
#[allow(non_camel_case_types)]
pub type pthread_t = *mut ffi::c_void;
pub const _SC_NPROCESSORS_ONLN: ffi::c_int = 84;
unsafe extern "C" {
pub fn pthread_create(
native: *mut pthread_t,
attr: *const pthread_attr_t,
f: extern "C" fn(*mut ffi::c_void) -> *mut ffi::c_void,
value: *mut ffi::c_void,
) -> ffi::c_int;
pub fn pthread_join(native: pthread_t, value: *mut *mut ffi::c_void) -> ffi::c_int;
pub fn pthread_attr_init(attrp: *mut pthread_attr_t) -> ffi::c_int;
pub fn pthread_attr_setstacksize(
attr: *mut pthread_attr_t,
stack_size: libc::size_t,
) -> ffi::c_int;
pub fn pthread_attr_destroy(attr: *mut pthread_attr_t) -> ffi::c_int;
pub fn pthread_detach(thread: pthread_t) -> ffi::c_int;
}
}
pub struct Thread {
id: libc::pthread_t,
}
impl Drop for Thread {
fn drop(&mut self) {
let ret = unsafe { libc::pthread_detach(self.id) };
debug_assert_eq!(ret, 0);
}
}
// defined in wasi-libc
// https://github.com/WebAssembly/wasi-libc/blob/a6f871343313220b76009827ed0153586361c0d5/libc-top-half/musl/include/alltypes.h.in#L108
#[repr(C)]
union pthread_attr_union {
__i: [ffi::c_int; if size_of::<ffi::c_long>() == 8 { 14 } else { 9 }],
__vi: [ffi::c_int; if size_of::<ffi::c_long>() == 8 { 14 } else { 9 }],
__s: [ffi::c_ulong; if size_of::<ffi::c_long>() == 8 { 7 } else { 9 }],
}
_ => {
pub struct Thread(!);
#[repr(C)]
pub struct pthread_attr_t {
__u: pthread_attr_union,
}
#[allow(non_camel_case_types)]
pub type pthread_t = *mut ffi::c_void;
pub const _SC_NPROCESSORS_ONLN: ffi::c_int = 84;
unsafe extern "C" {
pub fn pthread_create(
native: *mut pthread_t,
attr: *const pthread_attr_t,
f: extern "C" fn(*mut ffi::c_void) -> *mut ffi::c_void,
value: *mut ffi::c_void,
) -> ffi::c_int;
pub fn pthread_join(native: pthread_t, value: *mut *mut ffi::c_void) -> ffi::c_int;
pub fn pthread_attr_init(attrp: *mut pthread_attr_t) -> ffi::c_int;
pub fn pthread_attr_setstacksize(
attr: *mut pthread_attr_t,
stack_size: libc::size_t,
) -> ffi::c_int;
pub fn pthread_attr_destroy(attr: *mut pthread_attr_t) -> ffi::c_int;
pub fn pthread_detach(thread: pthread_t) -> ffi::c_int;
}
}
#[cfg(target_feature = "atomics")]
pub struct Thread {
id: libc::pthread_t,
}
#[cfg(target_feature = "atomics")]
impl Drop for Thread {
fn drop(&mut self) {
let ret = unsafe { libc::pthread_detach(self.id) };
debug_assert_eq!(ret, 0);
}
}
pub const DEFAULT_MIN_STACK_SIZE: usize = 1024 * 1024;
#[cfg(target_feature = "atomics")]
impl Thread {
// unsafe: see thread::Builder::spawn_unchecked for safety requirements
cfg_select! {
target_feature = "atomics" => {
pub unsafe fn new(stack: usize, _name: Option<&str>, p: Box<dyn FnOnce()>) -> io::Result<Thread> {
let p = Box::into_raw(Box::new(p));
let mut native: libc::pthread_t = unsafe { mem::zeroed() };
let mut attr: libc::pthread_attr_t = unsafe { mem::zeroed() };
assert_eq!(unsafe { libc::pthread_attr_init(&mut attr) }, 0);
pub unsafe fn new(
stack: usize,
_name: Option<&str>,
p: Box<dyn FnOnce()>,
) -> io::Result<Thread> {
let p = Box::into_raw(Box::new(p));
let mut native: libc::pthread_t = unsafe { mem::zeroed() };
let mut attr: libc::pthread_attr_t = unsafe { mem::zeroed() };
assert_eq!(unsafe { libc::pthread_attr_init(&mut attr) }, 0);
let stack_size = cmp::max(stack, DEFAULT_MIN_STACK_SIZE);
let stack_size = cmp::max(stack, DEFAULT_MIN_STACK_SIZE);
match unsafe { libc::pthread_attr_setstacksize(&mut attr, stack_size) } {
0 => {}
n => {
assert_eq!(n, libc::EINVAL);
// EINVAL means |stack_size| is either too small or not a
// multiple of the system page size. Because it's definitely
// >= PTHREAD_STACK_MIN, it must be an alignment issue.
// Round up to the nearest page and try again.
let page_size = os::page_size();
let stack_size =
(stack_size + page_size - 1) & (-(page_size as isize - 1) as usize - 1);
assert_eq!(unsafe { libc::pthread_attr_setstacksize(&mut attr, stack_size) }, 0);
}
};
let ret = unsafe { libc::pthread_create(&mut native, &attr, thread_start, p as *mut _) };
// Note: if the thread creation fails and this assert fails, then p will
// be leaked. However, an alternative design could cause double-free
// which is clearly worse.
assert_eq!(unsafe {libc::pthread_attr_destroy(&mut attr) }, 0);
return if ret != 0 {
// The thread failed to start and as a result p was not consumed. Therefore, it is
// safe to reconstruct the box so that it gets deallocated.
unsafe { drop(Box::from_raw(p)); }
Err(io::Error::from_raw_os_error(ret))
} else {
Ok(Thread { id: native })
};
extern "C" fn thread_start(main: *mut libc::c_void) -> *mut libc::c_void {
unsafe {
// Finally, let's run some code.
Box::from_raw(main as *mut Box<dyn FnOnce()>)();
}
ptr::null_mut()
}
match unsafe { libc::pthread_attr_setstacksize(&mut attr, stack_size) } {
0 => {}
n => {
assert_eq!(n, libc::EINVAL);
// EINVAL means |stack_size| is either too small or not a
// multiple of the system page size. Because it's definitely
// >= PTHREAD_STACK_MIN, it must be an alignment issue.
// Round up to the nearest page and try again.
let page_size = os::page_size();
let stack_size =
(stack_size + page_size - 1) & (-(page_size as isize - 1) as usize - 1);
assert_eq!(unsafe { libc::pthread_attr_setstacksize(&mut attr, stack_size) }, 0);
}
}
_ => {
pub unsafe fn new(_stack: usize, _name: Option<&str>, _p: Box<dyn FnOnce()>) -> io::Result<Thread> {
crate::sys::unsupported()
}
}
}
};
pub fn yield_now() {
let ret = unsafe { wasi::sched_yield() };
debug_assert_eq!(ret, Ok(()));
}
let ret = unsafe { libc::pthread_create(&mut native, &attr, thread_start, p as *mut _) };
// Note: if the thread creation fails and this assert fails, then p will
// be leaked. However, an alternative design could cause double-free
// which is clearly worse.
assert_eq!(unsafe { libc::pthread_attr_destroy(&mut attr) }, 0);
pub fn set_name(_name: &CStr) {
// nope
}
pub fn sleep(dur: Duration) {
let mut nanos = dur.as_nanos();
while nanos > 0 {
const USERDATA: wasi::Userdata = 0x0123_45678;
let clock = wasi::SubscriptionClock {
id: wasi::CLOCKID_MONOTONIC,
timeout: u64::try_from(nanos).unwrap_or(u64::MAX),
precision: 0,
flags: 0,
};
nanos -= u128::from(clock.timeout);
let in_ = wasi::Subscription {
userdata: USERDATA,
u: wasi::SubscriptionU { tag: 0, u: wasi::SubscriptionUU { clock } },
};
return if ret != 0 {
// The thread failed to start and as a result p was not consumed. Therefore, it is
// safe to reconstruct the box so that it gets deallocated.
unsafe {
let mut event: wasi::Event = mem::zeroed();
let res = wasi::poll_oneoff(&in_, &mut event, 1);
match (res, event) {
(
Ok(1),
wasi::Event {
userdata: USERDATA,
error: wasi::ERRNO_SUCCESS,
type_: wasi::EVENTTYPE_CLOCK,
..
},
) => {}
_ => panic!("thread::sleep(): unexpected result of poll_oneoff"),
}
drop(Box::from_raw(p));
}
}
}
Err(io::Error::from_raw_os_error(ret))
} else {
Ok(Thread { id: native })
};
pub fn sleep_until(deadline: Instant) {
let now = Instant::now();
if let Some(delay) = deadline.checked_duration_since(now) {
Self::sleep(delay);
extern "C" fn thread_start(main: *mut libc::c_void) -> *mut libc::c_void {
unsafe {
// Finally, let's run some code.
Box::from_raw(main as *mut Box<dyn FnOnce()>)();
}
ptr::null_mut()
}
}
pub fn join(self) {
cfg_select! {
target_feature = "atomics" => {
let id = mem::ManuallyDrop::new(self).id;
let ret = unsafe { libc::pthread_join(id, ptr::null_mut()) };
if ret != 0 {
rtabort!("failed to join thread: {}", io::Error::from_raw_os_error(ret));
}
}
_ => {
self.0
}
let id = mem::ManuallyDrop::new(self).id;
let ret = unsafe { libc::pthread_join(id, ptr::null_mut()) };
if ret != 0 {
rtabort!("failed to join thread: {}", io::Error::from_raw_os_error(ret));
}
}
}
pub(crate) fn current_os_id() -> Option<u64> {
None
}
#[cfg(target_feature = "atomics")]
pub fn available_parallelism() -> io::Result<NonZero<usize>> {
cfg_select! {
target_feature = "atomics" => {
match unsafe { libc::sysconf(libc::_SC_NPROCESSORS_ONLN) } {
-1 => Err(io::Error::last_os_error()),
cpus => NonZero::new(cpus as usize).ok_or(io::Error::UNKNOWN_THREAD_COUNT),
}
}
_ => crate::sys::unsupported(),
match unsafe { libc::sysconf(libc::_SC_NPROCESSORS_ONLN) } {
-1 => Err(io::Error::last_os_error()),
cpus => NonZero::new(cpus as usize).ok_or(io::Error::UNKNOWN_THREAD_COUNT),
}
}
pub fn yield_now() {
let ret = unsafe { wasi::sched_yield() };
debug_assert_eq!(ret, Ok(()));
}
pub fn sleep(dur: Duration) {
let mut nanos = dur.as_nanos();
while nanos > 0 {
const USERDATA: wasi::Userdata = 0x0123_45678;
let clock = wasi::SubscriptionClock {
id: wasi::CLOCKID_MONOTONIC,
timeout: u64::try_from(nanos).unwrap_or(u64::MAX),
precision: 0,
flags: 0,
};
nanos -= u128::from(clock.timeout);
let in_ = wasi::Subscription {
userdata: USERDATA,
u: wasi::SubscriptionU { tag: 0, u: wasi::SubscriptionUU { clock } },
};
unsafe {
let mut event: wasi::Event = mem::zeroed();
let res = wasi::poll_oneoff(&in_, &mut event, 1);
match (res, event) {
(
Ok(1),
wasi::Event {
userdata: USERDATA,
error: wasi::ERRNO_SUCCESS,
type_: wasi::EVENTTYPE_CLOCK,
..
},
) => {}
_ => panic!("thread::sleep(): unexpected result of poll_oneoff"),
}
}
}
}

View file

@ -1,73 +1,32 @@
use crate::ffi::CStr;
use crate::io;
use crate::num::NonZero;
use crate::time::{Duration, Instant};
pub struct Thread(!);
pub const DEFAULT_MIN_STACK_SIZE: usize = 1024 * 1024;
impl Thread {
pub unsafe fn new(
_stack: usize,
_name: Option<&str>,
_p: Box<dyn FnOnce()>,
) -> io::Result<Thread> {
// Note that unlike WASIp1 even if the wasm `atomics` feature is enabled
// there is no support for threads, not even experimentally, not even in
// wasi-libc. Thus this is unconditionally unsupported.
crate::sys::unsupported()
pub fn sleep(dur: Duration) {
// Sleep in increments of `u64::MAX` nanoseconds until the `dur` is
// entirely drained.
let mut remaining = dur.as_nanos();
while remaining > 0 {
let amt = u64::try_from(remaining).unwrap_or(u64::MAX);
wasip2::clocks::monotonic_clock::subscribe_duration(amt).block();
remaining -= u128::from(amt);
}
}
pub fn yield_now() {
// no API for this in WASIp2, but there's also no threads, so that's
// sort of expected.
}
pub fn set_name(_name: &CStr) {
// nope
}
pub fn sleep(dur: Duration) {
// Sleep in increments of `u64::MAX` nanoseconds until the `dur` is
// entirely drained.
let mut remaining = dur.as_nanos();
while remaining > 0 {
let amt = u64::try_from(remaining).unwrap_or(u64::MAX);
wasip2::clocks::monotonic_clock::subscribe_duration(amt).block();
remaining -= u128::from(amt);
pub fn sleep_until(deadline: Instant) {
match u64::try_from(deadline.into_inner().as_duration().as_nanos()) {
// If the point in time we're sleeping to fits within a 64-bit
// number of nanoseconds then directly use `subscribe_instant`.
Ok(deadline) => {
wasip2::clocks::monotonic_clock::subscribe_instant(deadline).block();
}
}
// ... otherwise we're sleeping for 500+ years relative to the
// "start" of what the system is using as a clock so speed/accuracy
// is not so much of a concern. Use `sleep` instead.
Err(_) => {
let now = Instant::now();
pub fn sleep_until(deadline: Instant) {
match u64::try_from(deadline.into_inner().as_duration().as_nanos()) {
// If the point in time we're sleeping to fits within a 64-bit
// number of nanoseconds then directly use `subscribe_instant`.
Ok(deadline) => {
wasip2::clocks::monotonic_clock::subscribe_instant(deadline).block();
}
// ... otherwise we're sleeping for 500+ years relative to the
// "start" of what the system is using as a clock so speed/accuracy
// is not so much of a concern. Use `sleep` instead.
Err(_) => {
let now = Instant::now();
if let Some(delay) = deadline.checked_duration_since(now) {
Self::sleep(delay);
}
if let Some(delay) = deadline.checked_duration_since(now) {
sleep(delay);
}
}
}
pub fn join(self) {
self.0
}
}
pub(crate) fn current_os_id() -> Option<u64> {
None
}
pub fn available_parallelism() -> io::Result<NonZero<usize>> {
crate::sys::unsupported()
}

View file

@ -1,75 +1,23 @@
use crate::ffi::CStr;
use crate::io;
use crate::num::NonZero;
use crate::sys::unsupported;
use crate::time::{Duration, Instant};
use crate::cmp;
use crate::time::Duration;
pub struct Thread(!);
pub fn sleep(dur: Duration) {
#[cfg(target_arch = "wasm32")]
use core::arch::wasm32 as wasm;
#[cfg(target_arch = "wasm64")]
use core::arch::wasm64 as wasm;
pub const DEFAULT_MIN_STACK_SIZE: usize = 1024 * 1024;
impl Thread {
// unsafe: see thread::Builder::spawn_unchecked for safety requirements
pub unsafe fn new(
_stack: usize,
_name: Option<&str>,
_p: Box<dyn FnOnce()>,
) -> io::Result<Thread> {
unsupported()
}
pub fn yield_now() {}
pub fn set_name(_name: &CStr) {}
pub fn sleep(dur: Duration) {
#[cfg(target_arch = "wasm32")]
use core::arch::wasm32 as wasm;
#[cfg(target_arch = "wasm64")]
use core::arch::wasm64 as wasm;
use crate::cmp;
// Use an atomic wait to block the current thread artificially with a
// timeout listed. Note that we should never be notified (return value
// of 0) or our comparison should never fail (return value of 1) so we
// should always only resume execution through a timeout (return value
// 2).
let mut nanos = dur.as_nanos();
while nanos > 0 {
let amt = cmp::min(i64::MAX as u128, nanos);
let mut x = 0;
let val = unsafe { wasm::memory_atomic_wait32(&mut x, 0, amt as i64) };
debug_assert_eq!(val, 2);
nanos -= amt;
}
}
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) {}
}
pub(crate) fn current_os_id() -> Option<u64> {
None
}
pub fn available_parallelism() -> io::Result<NonZero<usize>> {
unsupported()
}
pub mod guard {
pub type Guard = !;
pub unsafe fn current() -> Option<Guard> {
None
}
pub unsafe fn init() -> Option<Guard> {
None
// Use an atomic wait to block the current thread artificially with a
// timeout listed. Note that we should never be notified (return value
// of 0) or our comparison should never fail (return value of 1) so we
// should always only resume execution through a timeout (return value
// 2).
let mut nanos = dur.as_nanos();
while nanos > 0 {
let amt = cmp::min(i64::MAX as u128, nanos);
let mut x = 0;
let val = unsafe { wasm::memory_atomic_wait32(&mut x, 0, amt as i64) };
debug_assert_eq!(val, 2);
nanos -= amt;
}
}

View file

@ -1,14 +1,14 @@
use core::ffi::c_void;
use super::time::WaitableTimer;
use super::to_u16s;
use crate::ffi::CStr;
use crate::num::NonZero;
use crate::os::windows::io::{AsRawHandle, HandleOrNull};
use crate::sys::handle::Handle;
use crate::sys::pal::time::WaitableTimer;
use crate::sys::pal::{dur2timeout, to_u16s};
use crate::sys::{c, stack_overflow};
use crate::sys_common::FromInner;
use crate::time::{Duration, Instant};
use crate::time::Duration;
use crate::{io, ptr};
pub const DEFAULT_MIN_STACK_SIZE: usize = 2 * 1024 * 1024;
@ -62,24 +62,6 @@ impl Thread {
}
}
pub fn set_name(name: &CStr) {
if let Ok(utf8) = name.to_str() {
if let Ok(utf16) = to_u16s(utf8) {
unsafe {
// SAFETY: the vec returned by `to_u16s` ends with a zero value
Self::set_name_wide(&utf16)
}
};
};
}
/// # Safety
///
/// `name` must end with a zero value
pub unsafe fn set_name_wide(name: &[u16]) {
unsafe { c::SetThreadDescription(c::GetCurrentThread(), name.as_ptr()) };
}
pub fn join(self) {
let rc = unsafe { c::WaitForSingleObject(self.handle.as_raw_handle(), c::INFINITE) };
if rc == c::WAIT_FAILED {
@ -87,37 +69,6 @@ impl Thread {
}
}
pub fn yield_now() {
// This function will return 0 if there are no other threads to execute,
// but this also means that the yield was useless so this isn't really a
// case that needs to be worried about.
unsafe {
c::SwitchToThread();
}
}
pub fn sleep(dur: Duration) {
fn high_precision_sleep(dur: Duration) -> Result<(), ()> {
let timer = WaitableTimer::high_resolution()?;
timer.set(dur)?;
timer.wait()
}
// Attempt to use high-precision sleep (Windows 10, version 1803+).
// On error fallback to the standard `Sleep` function.
// Also preserves the zero duration behavior of `Sleep`.
if dur.is_zero() || high_precision_sleep(dur).is_err() {
unsafe { c::Sleep(super::dur2timeout(dur)) }
}
}
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
}
@ -127,14 +78,6 @@ impl Thread {
}
}
pub(crate) fn current_os_id() -> Option<u64> {
// SAFETY: FFI call with no preconditions.
let id: u32 = unsafe { c::GetCurrentThreadId() };
// A return value of 0 indicates failed lookup.
if id == 0 { None } else { Some(id.into()) }
}
pub fn available_parallelism() -> io::Result<NonZero<usize>> {
let res = unsafe {
let mut sysinfo: c::SYSTEM_INFO = crate::mem::zeroed();
@ -146,3 +89,52 @@ pub fn available_parallelism() -> io::Result<NonZero<usize>> {
cpus => Ok(unsafe { NonZero::new_unchecked(cpus) }),
}
}
pub fn current_os_id() -> Option<u64> {
// SAFETY: FFI call with no preconditions.
let id: u32 = unsafe { c::GetCurrentThreadId() };
// A return value of 0 indicates failed lookup.
if id == 0 { None } else { Some(id.into()) }
}
pub fn set_name(name: &CStr) {
if let Ok(utf8) = name.to_str() {
if let Ok(utf16) = to_u16s(utf8) {
unsafe {
// SAFETY: the vec returned by `to_u16s` ends with a zero value
set_name_wide(&utf16)
}
};
};
}
/// # Safety
///
/// `name` must end with a zero value
pub unsafe fn set_name_wide(name: &[u16]) {
unsafe { c::SetThreadDescription(c::GetCurrentThread(), name.as_ptr()) };
}
pub fn sleep(dur: Duration) {
fn high_precision_sleep(dur: Duration) -> Result<(), ()> {
let timer = WaitableTimer::high_resolution()?;
timer.set(dur)?;
timer.wait()
}
// Attempt to use high-precision sleep (Windows 10, version 1803+).
// On error fallback to the standard `Sleep` function.
// Also preserves the zero duration behavior of `Sleep`.
if dur.is_zero() || high_precision_sleep(dur).is_err() {
unsafe { c::Sleep(dur2timeout(dur)) }
}
}
pub fn yield_now() {
// This function will return 0 if there are no other threads to execute,
// but this also means that the yield was useless so this isn't really a
// case that needs to be worried about.
unsafe {
c::SwitchToThread();
}
}

View file

@ -1,6 +1,5 @@
use core::arch::asm;
use crate::ffi::CStr;
use crate::io;
use crate::num::NonZero;
use crate::os::xous::ffi::{
@ -8,7 +7,7 @@ use crate::os::xous::ffi::{
map_memory, update_memory_flags,
};
use crate::os::xous::services::{TicktimerScalar, ticktimer_server};
use crate::time::{Duration, Instant};
use crate::time::Duration;
pub struct Thread {
tid: ThreadId,
@ -110,46 +109,29 @@ impl Thread {
Ok(Thread { tid })
}
pub fn yield_now() {
do_yield();
}
pub fn set_name(_name: &CStr) {
// nope
}
pub fn sleep(dur: Duration) {
// Because the sleep server works on units of `usized milliseconds`, split
// the messages up into these chunks. This means we may run into issues
// if you try to sleep a thread for more than 49 days on a 32-bit system.
let mut millis = dur.as_millis();
while millis > 0 {
let sleep_duration =
if millis > (usize::MAX as _) { usize::MAX } else { millis as usize };
blocking_scalar(ticktimer_server(), TicktimerScalar::SleepMs(sleep_duration).into())
.expect("failed to send message to ticktimer server");
millis -= sleep_duration as u128;
}
}
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();
}
}
pub(crate) fn current_os_id() -> Option<u64> {
None
}
pub fn available_parallelism() -> io::Result<NonZero<usize>> {
// We're unicore right now.
Ok(unsafe { NonZero::new_unchecked(1) })
}
pub fn yield_now() {
do_yield();
}
pub fn sleep(dur: Duration) {
// Because the sleep server works on units of `usized milliseconds`, split
// the messages up into these chunks. This means we may run into issues
// if you try to sleep a thread for more than 49 days on a 32-bit system.
let mut millis = dur.as_millis();
while millis > 0 {
let sleep_duration = if millis > (usize::MAX as _) { usize::MAX } else { millis as usize };
blocking_scalar(ticktimer_server(), TicktimerScalar::SleepMs(sleep_duration).into())
.expect("failed to send message to ticktimer server");
millis -= sleep_duration as u128;
}
}

View file

@ -550,7 +550,7 @@ impl Builder {
}
if let Some(name) = their_thread.cname() {
imp::Thread::set_name(name);
imp::set_name(name);
}
let f = f.into_inner();
@ -763,7 +763,7 @@ where
/// [`Mutex`]: crate::sync::Mutex
#[stable(feature = "rust1", since = "1.0.0")]
pub fn yield_now() {
imp::Thread::yield_now()
imp::yield_now()
}
/// Determines whether the current thread is unwinding because of panic.
@ -884,7 +884,7 @@ pub fn sleep_ms(ms: u32) {
/// ```
#[stable(feature = "thread_sleep", since = "1.4.0")]
pub fn sleep(dur: Duration) {
imp::Thread::sleep(dur)
imp::sleep(dur)
}
/// Puts the current thread to sleep until the specified deadline has passed.
@ -983,7 +983,7 @@ pub fn sleep(dur: Duration) {
/// ```
#[unstable(feature = "thread_sleep_until", issue = "113752")]
pub fn sleep_until(deadline: Instant) {
imp::Thread::sleep_until(deadline)
imp::sleep_until(deadline)
}
/// Used to ensure that `park` and `park_timeout` do not unwind, as that can