Path: blob/main/base/src/sys/windows/platform_timer_utils.rs
5394 views
// Copyright 2022 The ChromiumOS Authors1// Use of this source code is governed by a BSD-style license that can be2// found in the LICENSE file.34use std::io;5use std::sync::OnceLock;6use std::thread::sleep;7use std::time::Duration;8use std::time::Instant;910use win_util::win32_wide_string;11use winapi::shared::minwindef;12use winapi::shared::minwindef::PULONG;13use winapi::shared::ntdef::NTSTATUS;14use winapi::shared::ntdef::ULONG;15use winapi::shared::ntstatus::STATUS_NOT_IMPLEMENTED;16use winapi::shared::ntstatus::STATUS_SUCCESS;17use winapi::um::libloaderapi::GetProcAddress;18use winapi::um::libloaderapi::LoadLibraryW;19use winapi::um::mmsystem::TIMERR_NOERROR;20use winapi::um::timeapi::timeBeginPeriod;21use winapi::um::timeapi::timeEndPeriod;22use winapi::um::winnt::BOOLEAN;2324use crate::warn;25use crate::Error;26use crate::Result;2728type NtQueryTimerResolutionFn = extern "system" fn(PULONG, PULONG, PULONG) -> NTSTATUS;29type NtSetTimerResolutionFn = extern "system" fn(ULONG, BOOLEAN, PULONG) -> NTSTATUS;3031struct NtTimerFuncs {32nt_query_timer_resolution: NtQueryTimerResolutionFn,33nt_set_timer_resolution: NtSetTimerResolutionFn,34}3536static NT_TIMER_FUNCS: OnceLock<NtTimerFuncs> = OnceLock::new();3738fn init_nt_timer_funcs() -> NtTimerFuncs {39// SAFETY: return value is checked.40let handle = unsafe { LoadLibraryW(win32_wide_string("ntdll").as_ptr()) };41if handle.is_null() {42warn!("Failed to load ntdll: {}", Error::last());43return NtTimerFuncs {44nt_query_timer_resolution: nt_query_timer_resolution_fallback,45nt_set_timer_resolution: nt_set_timer_resolution_fallback,46};47}4849// SAFETY: return value is checked.50let query = unsafe { GetProcAddress(handle, c"NtQueryTimerResolution".as_ptr()) };51let nt_query_timer_resolution = if query.is_null() {52nt_query_timer_resolution_fallback53} else {54// SAFETY: the function signature matches.55unsafe {56std::mem::transmute::<*mut minwindef::__some_function, NtQueryTimerResolutionFn>(query)57}58};5960// SAFETY: return value is checked.61let set = unsafe { GetProcAddress(handle, c"NtSetTimerResolution".as_ptr()) };62let nt_set_timer_resolution = if set.is_null() {63nt_set_timer_resolution_fallback64} else {65// SAFETY: the function signature matches.66unsafe {67std::mem::transmute::<*mut minwindef::__some_function, NtSetTimerResolutionFn>(set)68}69};7071NtTimerFuncs {72nt_query_timer_resolution,73nt_set_timer_resolution,74}75}7677// This function is only used if NtQueryTimerResolution() is not available.78extern "system" fn nt_query_timer_resolution_fallback(_: PULONG, _: PULONG, _: PULONG) -> NTSTATUS {79STATUS_NOT_IMPLEMENTED80}8182// This function is only used if NtSetTimerResolution() is not available.83extern "system" fn nt_set_timer_resolution_fallback(_: ULONG, _: BOOLEAN, _: PULONG) -> NTSTATUS {84STATUS_NOT_IMPLEMENTED85}8687/// Returns the resolution of timers on the host (current_res, max_res).88pub fn nt_query_timer_resolution() -> Result<(Duration, Duration)> {89let funcs = NT_TIMER_FUNCS.get_or_init(init_nt_timer_funcs);9091let mut min_res: u32 = 0;92let mut max_res: u32 = 0;93let mut current_res: u32 = 0;94let ret = (funcs.nt_query_timer_resolution)(95&mut min_res as *mut u32,96&mut max_res as *mut u32,97&mut current_res as *mut u32,98);99100if ret != STATUS_SUCCESS {101Err(Error::from(io::Error::other(102"NtQueryTimerResolution failed",103)))104} else {105Ok((106Duration::from_nanos((current_res as u64) * 100),107Duration::from_nanos((max_res as u64) * 100),108))109}110}111112pub fn nt_set_timer_resolution(resolution: Duration) -> Result<()> {113let funcs = NT_TIMER_FUNCS.get_or_init(init_nt_timer_funcs);114115let requested_res: u32 = (resolution.as_nanos() / 100) as u32;116let mut current_res: u32 = 0;117let ret = (funcs.nt_set_timer_resolution)(118requested_res,1191, /* true */120&mut current_res as *mut u32,121);122123if ret != STATUS_SUCCESS {124Err(Error::from(io::Error::other("NtSetTimerResolution failed")))125} else {126Ok(())127}128}129130/// Measures the timer resolution by taking the 90th percentile wall time of 1ms sleeps.131pub fn measure_timer_resolution() -> Duration {132let mut durations = Vec::with_capacity(100);133for _ in 0..100 {134let start = Instant::now();135// Windows cannot support sleeps shorter than 1ms.136sleep(Duration::from_millis(1));137durations.push(Instant::now() - start);138}139140durations.sort();141durations[89]142}143144/// Note that Durations below 1ms are not supported and will panic.145pub fn set_time_period(res: Duration, begin: bool) -> Result<()> {146if res.as_millis() < 1 {147panic!(148"time(Begin|End)Period does not support values below 1ms, but {res:?} was requested."149);150}151if res.as_millis() > u32::MAX as u128 {152panic!("time(Begin|End)Period does not support values above u32::MAX.",);153}154155let ret = if begin {156// SAFETY: Trivially safe. Note that the casts are safe because we know res is within u32's157// range.158unsafe { timeBeginPeriod(res.as_millis() as u32) }159} else {160// SAFETY: Trivially safe. Note that the casts are safe because we know res is within u32's161// range.162unsafe { timeEndPeriod(res.as_millis() as u32) }163};164if ret != TIMERR_NOERROR {165// These functions only have two return codes: NOERROR and NOCANDO.166Err(Error::from(io::Error::new(167io::ErrorKind::InvalidInput,168"timeBegin/EndPeriod failed",169)))170} else {171Ok(())172}173}174175/// Note that these tests cannot run on Kokoro due to random slowness in that environment.176#[cfg(test)]177mod tests {178use super::*;179180/// We're testing whether NtSetTimerResolution does what it says on the tin.181#[test]182#[ignore]183fn setting_nt_timer_resolution_changes_resolution() {184let (old_res, _) = nt_query_timer_resolution().unwrap();185186nt_set_timer_resolution(Duration::from_millis(1)).unwrap();187assert_res_within_bound(measure_timer_resolution());188nt_set_timer_resolution(old_res).unwrap();189}190191#[test]192#[ignore]193fn setting_timer_resolution_changes_resolution() {194let res = Duration::from_millis(1);195196set_time_period(res, true).unwrap();197assert_res_within_bound(measure_timer_resolution());198set_time_period(res, false).unwrap();199}200201fn assert_res_within_bound(actual_res: Duration) {202assert!(203actual_res <= Duration::from_millis(2),204"actual_res was {actual_res:?}, expected <= 2ms"205);206}207}208209210