Path: blob/main/base/src/sys/windows/platform_timer_resolution.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::time::Duration;56use super::platform_timer_utils::measure_timer_resolution;7use super::platform_timer_utils::nt_query_timer_resolution;8use super::platform_timer_utils::nt_set_timer_resolution;9use super::platform_timer_utils::set_time_period;10use crate::info;11use crate::warn;12use crate::EnabledHighResTimer;13use crate::Result;1415/// Restores the Windows platform timer resolution to its original value on Drop.16struct NtSetTimerResolution {17previous_duration: Duration,18nt_set_timer_res: fn(Duration) -> Result<()>,19}20impl EnabledHighResTimer for NtSetTimerResolution {}2122impl NtSetTimerResolution {23fn new(24query_timer_res: fn() -> Result<(Duration, Duration)>,25nt_set_timer_res: fn(Duration) -> Result<()>,26measure_timer_res: fn() -> Duration,27) -> Option<NtSetTimerResolution> {28match query_timer_res() {29Ok((current_res, max_res)) if max_res <= Duration::from_micros(500) => {30match nt_set_timer_res(Duration::from_micros(500)) {31Ok(()) => {32let actual_res = measure_timer_res();33if actual_res < Duration::from_millis(2) {34info!("Successfully set timer res to 0.5ms with NtSetTimerResolution. Measured {}us.", actual_res.as_micros());35Some(NtSetTimerResolution {36previous_duration: current_res,37nt_set_timer_res,38})39} else {40warn!(41"Set timer res to 0.5ms with NtSetTimerResolution, but measured {}us.",42actual_res.as_micros()43);44None45}46}47Err(e) => {48warn!(49"Failed to execute NtSetTimeResolution, got error code: {}",50e51);52None53}54}55}56Ok((_, max_res)) => {57info!(58"System does not support 0.5ms timer. Max res: {}us",59max_res.as_micros()60);61None62}63Err(e) => {64warn!(65"Failed to execute NtQueryTimeResolution, got error code: {}",66e67);68None69}70}71}72}7374struct TimeBeginPeriod {75time_period_setter: fn(Duration, bool) -> Result<()>,76}77impl EnabledHighResTimer for TimeBeginPeriod {}7879impl Drop for NtSetTimerResolution {80fn drop(&mut self) {81if let Err(e) = (self.nt_set_timer_res)(self.previous_duration) {82warn!("Failed to unset nt timer resolution w/ error code: {}", e)83}84}85}8687impl Drop for TimeBeginPeriod {88fn drop(&mut self) {89if let Err(e) = (self.time_period_setter)(Duration::from_millis(1), false) {90warn!("Failed to unset timer resolution w/ error code: {}", e)91}92}93}9495/// Sets the Windows platform timer resolution to at least 1ms, or 0.5ms if possible on the96/// system.97pub fn enable_high_res_timers() -> Result<Box<dyn EnabledHighResTimer>> {98enable_high_res_timers_inner(99set_time_period,100nt_query_timer_resolution,101nt_set_timer_resolution,102measure_timer_resolution,103)104}105106fn enable_high_res_timers_inner(107time_period_setter: fn(Duration, bool) -> Result<()>,108query_timer_res: fn() -> Result<(Duration, Duration)>,109nt_set_timer_res: fn(Duration) -> Result<()>,110measure_timer_res: fn() -> Duration,111) -> Result<Box<dyn EnabledHighResTimer>> {112// Determine if possible to set 500 micro timer res, and if so proceed with using113// undocumented winapis to do so. In case of any failures using the undocumented APIs,114// we'll fall back on timeBegin/EndPeriod.115let nt_timer_res =116NtSetTimerResolution::new(query_timer_res, nt_set_timer_res, measure_timer_res);117118match nt_timer_res {119Some(timer_res) => Ok(Box::new(timer_res)),120None => {121time_period_setter(Duration::from_millis(1), true)?;122let actual_res = measure_timer_res();123if actual_res > Duration::from_millis(2) {124warn!(125"Set timer res to 1ms using timeBeginPeriod, but measured >2ms (measured: {}us).",126actual_res.as_micros(),127);128} else {129warn!(130"Set timer res to 1ms using timeBeginPeriod. Measured {}us.",131actual_res.as_micros(),132);133}134Ok(Box::new(TimeBeginPeriod { time_period_setter }))135}136}137}138139#[cfg(test)]140/// Note that nearly all of these tests cannot run on Kokoro due to random slowness in that141/// environment.142mod tests {143use super::*;144use crate::Error;145146fn time_period_setter_broken(_d: Duration, _b: bool) -> Result<()> {147Err(Error::new(100))148}149150fn query_timer_failure() -> Result<(Duration, Duration)> {151Err(Error::new(100))152}153154fn query_timer_res_high_res_available() -> Result<(Duration, Duration)> {155Ok((Duration::from_millis(15), Duration::from_micros(500)))156}157158fn query_timer_res_high_res_unavailable() -> Result<(Duration, Duration)> {159Ok((Duration::from_millis(15), Duration::from_millis(1)))160}161162fn nt_set_timer_res_broken(_: Duration) -> Result<()> {163Err(Error::new(100))164}165fn measure_timer_res_1ms() -> Duration {166Duration::from_millis(1)167}168169fn measure_timer_res_2ms() -> Duration {170Duration::from_millis(2)171}172173fn assert_res_within_bound(actual_res: Duration) {174assert!(175actual_res <= Duration::from_millis(2),176"actual_res was {actual_res:?}, expected <= 2ms"177);178}179180// Note that on some systems, a <1ms timer is not available. In these cases, this test181// will not exercise the NtSetTimerResolution path.182#[test]183#[ignore]184fn test_nt_timer_works() {185let _timer_res = enable_high_res_timers_inner(186set_time_period,187nt_query_timer_resolution,188nt_set_timer_resolution,189measure_timer_res_1ms,190)191.unwrap();192assert_res_within_bound(measure_timer_resolution())193}194195#[test]196#[ignore]197fn test_nt_timer_falls_back_on_failure() {198let _timer_res = enable_high_res_timers_inner(199set_time_period,200query_timer_res_high_res_available,201nt_set_timer_res_broken,202measure_timer_res_1ms,203)204.unwrap();205assert_res_within_bound(measure_timer_resolution())206}207208#[test]209#[ignore]210fn test_nt_timer_falls_back_on_measurement_failure() {211let _timer_res = enable_high_res_timers_inner(212set_time_period,213query_timer_res_high_res_available,214nt_set_timer_res_broken,215measure_timer_res_2ms,216)217.unwrap();218assert_res_within_bound(measure_timer_resolution())219}220221#[test]222#[ignore]223fn test_nt_timer_falls_back_on_low_res_system() {224let _timer_res = enable_high_res_timers_inner(225set_time_period,226query_timer_res_high_res_unavailable,227nt_set_timer_res_broken,228measure_timer_res_1ms,229)230.unwrap();231assert_res_within_bound(measure_timer_resolution())232}233234#[test]235#[ignore]236fn test_nt_timer_falls_back_on_query_failure() {237let _timer_res = enable_high_res_timers_inner(238set_time_period,239query_timer_failure,240nt_set_timer_res_broken,241measure_timer_res_1ms,242)243.unwrap();244assert_res_within_bound(measure_timer_resolution())245}246247#[test]248fn test_all_timer_sets_fail() {249assert!(enable_high_res_timers_inner(250time_period_setter_broken,251query_timer_failure,252nt_set_timer_res_broken,253measure_timer_res_1ms,254)255.is_err());256}257}258259260