Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Windows: Support sub-millisecond sleep #116461

Merged
merged 2 commits into from
Oct 24, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 6 additions & 0 deletions library/std/src/sys/windows/c/windows_sys.lst
Original file line number Diff line number Diff line change
Expand Up @@ -2503,9 +2503,12 @@ Windows.Win32.System.Threading.CREATE_SEPARATE_WOW_VDM
Windows.Win32.System.Threading.CREATE_SHARED_WOW_VDM
Windows.Win32.System.Threading.CREATE_SUSPENDED
Windows.Win32.System.Threading.CREATE_UNICODE_ENVIRONMENT
Windows.Win32.System.Threading.CREATE_WAITABLE_TIMER_HIGH_RESOLUTION
Windows.Win32.System.Threading.CREATE_WAITABLE_TIMER_MANUAL_RESET
Windows.Win32.System.Threading.CreateEventW
Windows.Win32.System.Threading.CreateProcessW
Windows.Win32.System.Threading.CreateThread
Windows.Win32.System.Threading.CreateWaitableTimerExW
Windows.Win32.System.Threading.DEBUG_ONLY_THIS_PROCESS
Windows.Win32.System.Threading.DEBUG_PROCESS
Windows.Win32.System.Threading.DeleteProcThreadAttributeList
Expand Down Expand Up @@ -2542,6 +2545,7 @@ Windows.Win32.System.Threading.REALTIME_PRIORITY_CLASS
Windows.Win32.System.Threading.ReleaseSRWLockExclusive
Windows.Win32.System.Threading.ReleaseSRWLockShared
Windows.Win32.System.Threading.SetThreadStackGuarantee
Windows.Win32.System.Threading.SetWaitableTimer
Windows.Win32.System.Threading.Sleep
Windows.Win32.System.Threading.SleepConditionVariableSRW
Windows.Win32.System.Threading.SleepEx
Expand All @@ -2568,6 +2572,8 @@ Windows.Win32.System.Threading.TerminateProcess
Windows.Win32.System.Threading.THREAD_CREATE_RUN_IMMEDIATELY
Windows.Win32.System.Threading.THREAD_CREATE_SUSPENDED
Windows.Win32.System.Threading.THREAD_CREATION_FLAGS
Windows.Win32.System.Threading.TIMER_ALL_ACCESS
Windows.Win32.System.Threading.TIMER_MODIFY_STATE
Windows.Win32.System.Threading.TLS_OUT_OF_INDEXES
Windows.Win32.System.Threading.TlsAlloc
Windows.Win32.System.Threading.TlsFree
Expand Down
32 changes: 32 additions & 0 deletions library/std/src/sys/windows/c/windows_sys.rs
Original file line number Diff line number Diff line change
Expand Up @@ -151,6 +151,15 @@ extern "system" {
) -> HANDLE;
}
#[link(name = "kernel32")]
extern "system" {
pub fn CreateWaitableTimerExW(
lptimerattributes: *const SECURITY_ATTRIBUTES,
lptimername: PCWSTR,
dwflags: u32,
dwdesiredaccess: u32,
) -> HANDLE;
}
#[link(name = "kernel32")]
extern "system" {
pub fn DeleteFileW(lpfilename: PCWSTR) -> BOOL;
}
Expand Down Expand Up @@ -508,6 +517,17 @@ extern "system" {
pub fn SetThreadStackGuarantee(stacksizeinbytes: *mut u32) -> BOOL;
}
#[link(name = "kernel32")]
extern "system" {
pub fn SetWaitableTimer(
htimer: HANDLE,
lpduetime: *const i64,
lperiod: i32,
pfncompletionroutine: PTIMERAPCROUTINE,
lpargtocompletionroutine: *const ::core::ffi::c_void,
fresume: BOOL,
) -> BOOL;
}
#[link(name = "kernel32")]
extern "system" {
pub fn Sleep(dwmilliseconds: u32) -> ();
}
Expand Down Expand Up @@ -1164,6 +1184,8 @@ pub const CREATE_SEPARATE_WOW_VDM: PROCESS_CREATION_FLAGS = 2048u32;
pub const CREATE_SHARED_WOW_VDM: PROCESS_CREATION_FLAGS = 4096u32;
pub const CREATE_SUSPENDED: PROCESS_CREATION_FLAGS = 4u32;
pub const CREATE_UNICODE_ENVIRONMENT: PROCESS_CREATION_FLAGS = 1024u32;
pub const CREATE_WAITABLE_TIMER_HIGH_RESOLUTION: u32 = 2u32;
pub const CREATE_WAITABLE_TIMER_MANUAL_RESET: u32 = 1u32;
pub const CSTR_EQUAL: COMPARESTRING_RESULT = 2i32;
pub const CSTR_GREATER_THAN: COMPARESTRING_RESULT = 3i32;
pub const CSTR_LESS_THAN: COMPARESTRING_RESULT = 1i32;
Expand Down Expand Up @@ -3774,6 +3796,13 @@ pub const PROFILE_SERVER: PROCESS_CREATION_FLAGS = 1073741824u32;
pub const PROFILE_USER: PROCESS_CREATION_FLAGS = 268435456u32;
pub const PROGRESS_CONTINUE: u32 = 0u32;
pub type PSTR = *mut u8;
pub type PTIMERAPCROUTINE = ::core::option::Option<
unsafe extern "system" fn(
lpargtocompletionroutine: *const ::core::ffi::c_void,
dwtimerlowvalue: u32,
dwtimerhighvalue: u32,
) -> (),
>;
pub type PWSTR = *mut u16;
pub const READ_CONTROL: FILE_ACCESS_RIGHTS = 131072u32;
pub const REALTIME_PRIORITY_CLASS: PROCESS_CREATION_FLAGS = 256u32;
Expand Down Expand Up @@ -3910,6 +3939,7 @@ pub type SYMBOLIC_LINK_FLAGS = u32;
pub const SYMBOLIC_LINK_FLAG_ALLOW_UNPRIVILEGED_CREATE: SYMBOLIC_LINK_FLAGS = 2u32;
pub const SYMBOLIC_LINK_FLAG_DIRECTORY: SYMBOLIC_LINK_FLAGS = 1u32;
pub const SYMLINK_FLAG_RELATIVE: u32 = 1u32;
pub type SYNCHRONIZATION_ACCESS_RIGHTS = u32;
pub const SYNCHRONIZE: FILE_ACCESS_RIGHTS = 1048576u32;
#[repr(C)]
pub struct SYSTEM_INFO {
Expand Down Expand Up @@ -3956,6 +3986,8 @@ pub const TCP_NODELAY: i32 = 1i32;
pub const THREAD_CREATE_RUN_IMMEDIATELY: THREAD_CREATION_FLAGS = 0u32;
pub const THREAD_CREATE_SUSPENDED: THREAD_CREATION_FLAGS = 4u32;
pub type THREAD_CREATION_FLAGS = u32;
pub const TIMER_ALL_ACCESS: SYNCHRONIZATION_ACCESS_RIGHTS = 2031619u32;
pub const TIMER_MODIFY_STATE: SYNCHRONIZATION_ACCESS_RIGHTS = 2u32;
#[repr(C)]
pub struct TIMEVAL {
pub tv_sec: i32,
Expand Down
13 changes: 12 additions & 1 deletion library/std/src/sys/windows/thread.rs
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ use crate::time::Duration;

use libc::c_void;

use super::time::WaitableTimer;
use super::to_u16s;

pub const DEFAULT_MIN_STACK_SIZE: usize = 2 * 1024 * 1024;
Expand Down Expand Up @@ -87,7 +88,17 @@ impl Thread {
}

pub fn sleep(dur: Duration) {
unsafe { c::Sleep(super::dur2timeout(dur)) }
fn high_precision_sleep(dur: Duration) -> Result<(), ()> {
let timer = WaitableTimer::high_resolution()?;
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

How good is the performance of CreateWaitableTimerExW? If it is slow, it might be better to cache the timer in a thread local.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It's around 2,000 nanoseconds on my machine (minus the time it takes to call QueryPerformanceCounter). That is well within noise for waiting on my machine. Granted, other hardware may be able to perform better but I'd rather avoid adding a thread local unless or until someone can show it's worth it.

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 behaviour of `Sleep`.
if dur.is_zero() || high_precision_sleep(dur).is_err() {
unsafe { c::Sleep(super::dur2timeout(dur)) }
}
}

pub fn handle(&self) -> &Handle {
Expand Down
38 changes: 38 additions & 0 deletions library/std/src/sys/windows/time.rs
Original file line number Diff line number Diff line change
@@ -1,11 +1,13 @@
use crate::cmp::Ordering;
use crate::fmt;
use crate::mem;
use crate::ptr::{null, null_mut};
use crate::sys::c;
use crate::sys_common::IntoInner;
use crate::time::Duration;

use core::hash::{Hash, Hasher};
use core::ops::Neg;

const NANOS_PER_SEC: u64 = 1_000_000_000;
const INTERVALS_PER_SEC: u64 = NANOS_PER_SEC / 100;
Expand Down Expand Up @@ -222,3 +224,39 @@ mod perf_counter {
qpc_value
}
}

/// A timer you can wait on.
pub(super) struct WaitableTimer {
handle: c::HANDLE,
}
impl WaitableTimer {
/// Create a high-resolution timer. Will fail before Windows 10, version 1803.
pub fn high_resolution() -> Result<Self, ()> {
let handle = unsafe {
c::CreateWaitableTimerExW(
null(),
null(),
c::CREATE_WAITABLE_TIMER_HIGH_RESOLUTION,
c::TIMER_ALL_ACCESS,
)
};
if handle != null_mut() { Ok(Self { handle }) } else { Err(()) }
}
pub fn set(&self, duration: Duration) -> Result<(), ()> {
// Convert the Duration to a format similar to FILETIME.
// Negative values are relative times whereas positive values are absolute.
// Therefore we negate the relative duration.
let time = checked_dur2intervals(&duration).ok_or(())?.neg();
let result = unsafe { c::SetWaitableTimer(self.handle, &time, 0, None, null(), c::FALSE) };
if result != 0 { Ok(()) } else { Err(()) }
}
pub fn wait(&self) -> Result<(), ()> {
let result = unsafe { c::WaitForSingleObject(self.handle, c::INFINITE) };
if result != c::WAIT_FAILED { Ok(()) } else { Err(()) }
}
}
impl Drop for WaitableTimer {
fn drop(&mut self) {
unsafe { c::CloseHandle(self.handle) };
}
}
12 changes: 12 additions & 0 deletions src/tools/miri/src/shims/windows/foreign_items.rs
Original file line number Diff line number Diff line change
Expand Up @@ -267,6 +267,18 @@ pub trait EvalContextExt<'mir, 'tcx: 'mir>: crate::MiriInterpCxExt<'mir, 'tcx> {

this.Sleep(timeout)?;
}
"CreateWaitableTimerExW" => {
let [attributes, name, flags, access] =
this.check_shim(abi, Abi::System { unwind: false }, link_name, args)?;
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Generally we do at least read all the arguments of a shim to make sure they are initialized -- or we mark the shim as "only run in std".

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ok, I've added the reads.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks! LGTM.

this.read_pointer(attributes)?;
this.read_pointer(name)?;
this.read_scalar(flags)?.to_u32()?;
this.read_scalar(access)?.to_u32()?;
// Unimplemented. Always return failure.
let not_supported = this.eval_windows("c", "ERROR_NOT_SUPPORTED");
this.set_last_error(not_supported)?;
this.write_null(dest)?;
}

// Synchronization primitives
"AcquireSRWLockExclusive" => {
Expand Down
Loading