1 // Copyright 2022 The ChromiumOS Authors
2 // Use of this source code is governed by a BSD-style license that can be
3 // found in the LICENSE file.
4
5 use std::time::Duration;
6
7 use crate::info;
8 use crate::measure_timer_resolution;
9 use crate::nt_query_timer_resolution;
10 use crate::nt_set_timer_resolution;
11 use crate::set_time_period;
12 use crate::warn;
13 use crate::EnabledHighResTimer;
14 use crate::Result;
15
16 /// Restores the Windows platform timer resolution to its original value on Drop.
17 struct NtSetTimerResolution {
18 previous_duration: Duration,
19 nt_set_timer_res: fn(Duration) -> Result<()>,
20 }
21 impl EnabledHighResTimer for NtSetTimerResolution {}
22
23 impl NtSetTimerResolution {
new( query_timer_res: fn() -> Result<(Duration, Duration)>, nt_set_timer_res: fn(Duration) -> Result<()>, measure_timer_res: fn() -> Duration, ) -> Option<NtSetTimerResolution>24 fn new(
25 query_timer_res: fn() -> Result<(Duration, Duration)>,
26 nt_set_timer_res: fn(Duration) -> Result<()>,
27 measure_timer_res: fn() -> Duration,
28 ) -> Option<NtSetTimerResolution> {
29 match query_timer_res() {
30 Ok((current_res, max_res)) if max_res <= Duration::from_micros(500) => {
31 match nt_set_timer_res(Duration::from_micros(500)) {
32 Ok(()) => {
33 let actual_res = measure_timer_res();
34 if actual_res < Duration::from_millis(2) {
35 info!("Successfully set timer res to 0.5ms with NtSetTimerResolution. Measured {}us.", actual_res.as_micros());
36 Some(NtSetTimerResolution {
37 previous_duration: current_res,
38 nt_set_timer_res,
39 })
40 } else {
41 warn!(
42 "Set timer res to 0.5ms with NtSetTimerResolution, but measured {}us.",
43 actual_res.as_micros()
44 );
45 None
46 }
47 }
48 Err(e) => {
49 warn!(
50 "Failed to execute NtSetTimeResolution, got error code: {}",
51 e
52 );
53 None
54 }
55 }
56 }
57 Ok((_, max_res)) => {
58 info!(
59 "System does not support 0.5ms timer. Max res: {}us",
60 max_res.as_micros()
61 );
62 None
63 }
64 Err(e) => {
65 warn!(
66 "Failed to execute NtQueryTimeResolution, got error code: {}",
67 e
68 );
69 None
70 }
71 }
72 }
73 }
74
75 struct TimeBeginPeriod {
76 time_period_setter: fn(Duration, bool) -> Result<()>,
77 }
78 impl EnabledHighResTimer for TimeBeginPeriod {}
79
80 impl Drop for NtSetTimerResolution {
drop(&mut self)81 fn drop(&mut self) {
82 if let Err(e) = (self.nt_set_timer_res)(self.previous_duration) {
83 warn!("Failed to unset nt timer resolution w/ error code: {}", e)
84 }
85 }
86 }
87
88 impl Drop for TimeBeginPeriod {
drop(&mut self)89 fn drop(&mut self) {
90 if let Err(e) = (self.time_period_setter)(Duration::from_millis(1), false) {
91 warn!("Failed to unset timer resolution w/ error code: {}", e)
92 }
93 }
94 }
95
96 /// Sets the Windows platform timer resolution to at least 1ms, or 0.5ms if possible on the
97 /// system.
enable_high_res_timers() -> Result<Box<dyn EnabledHighResTimer>>98 pub fn enable_high_res_timers() -> Result<Box<dyn EnabledHighResTimer>> {
99 enable_high_res_timers_inner(
100 set_time_period,
101 nt_query_timer_resolution,
102 nt_set_timer_resolution,
103 measure_timer_resolution,
104 )
105 }
106
enable_high_res_timers_inner( time_period_setter: fn(Duration, bool) -> Result<()>, query_timer_res: fn() -> Result<(Duration, Duration)>, nt_set_timer_res: fn(Duration) -> Result<()>, measure_timer_res: fn() -> Duration, ) -> Result<Box<dyn EnabledHighResTimer>>107 fn enable_high_res_timers_inner(
108 time_period_setter: fn(Duration, bool) -> Result<()>,
109 query_timer_res: fn() -> Result<(Duration, Duration)>,
110 nt_set_timer_res: fn(Duration) -> Result<()>,
111 measure_timer_res: fn() -> Duration,
112 ) -> Result<Box<dyn EnabledHighResTimer>> {
113 // Determine if possible to set 500 micro timer res, and if so proceed with using
114 // undocumented winapis to do so. In case of any failures using the undocumented APIs,
115 // we'll fall back on timeBegin/EndPeriod.
116 let nt_timer_res =
117 NtSetTimerResolution::new(query_timer_res, nt_set_timer_res, measure_timer_res);
118
119 match nt_timer_res {
120 Some(timer_res) => Ok(Box::new(timer_res)),
121 None => {
122 time_period_setter(Duration::from_millis(1), true)?;
123 let actual_res = measure_timer_res();
124 if actual_res > Duration::from_millis(2) {
125 warn!(
126 "Set timer res to 1ms using timeBeginPeriod, but measured >2ms (measured: {}us).",
127 actual_res.as_micros(),
128 );
129 } else {
130 warn!(
131 "Set timer res to 1ms using timeBeginPeriod. Measured {}us.",
132 actual_res.as_micros(),
133 );
134 }
135 Ok(Box::new(TimeBeginPeriod { time_period_setter }))
136 }
137 }
138 }
139
140 #[cfg(test)]
141 /// Note that nearly all of these tests cannot run on Kokoro due to random slowness in that
142 /// environment.
143 mod tests {
144 use super::*;
145 use crate::Error;
146
time_period_setter_broken(_d: Duration, _b: bool) -> Result<()>147 fn time_period_setter_broken(_d: Duration, _b: bool) -> Result<()> {
148 Err(Error::new(100))
149 }
150
query_timer_failure() -> Result<(Duration, Duration)>151 fn query_timer_failure() -> Result<(Duration, Duration)> {
152 Err(Error::new(100))
153 }
154
query_timer_res_high_res_available() -> Result<(Duration, Duration)>155 fn query_timer_res_high_res_available() -> Result<(Duration, Duration)> {
156 Ok((Duration::from_millis(15), Duration::from_micros(500)))
157 }
158
query_timer_res_high_res_unavailable() -> Result<(Duration, Duration)>159 fn query_timer_res_high_res_unavailable() -> Result<(Duration, Duration)> {
160 Ok((Duration::from_millis(15), Duration::from_millis(1)))
161 }
162
nt_set_timer_res_broken(_: Duration) -> Result<()>163 fn nt_set_timer_res_broken(_: Duration) -> Result<()> {
164 Err(Error::new(100))
165 }
measure_timer_res_1ms() -> Duration166 fn measure_timer_res_1ms() -> Duration {
167 Duration::from_millis(1)
168 }
169
measure_timer_res_2ms() -> Duration170 fn measure_timer_res_2ms() -> Duration {
171 Duration::from_millis(2)
172 }
173
assert_res_within_bound(actual_res: Duration)174 fn assert_res_within_bound(actual_res: Duration) {
175 assert!(
176 actual_res <= Duration::from_millis(2),
177 "actual_res was {:?}, expected <= 2ms",
178 actual_res
179 );
180 }
181
182 // Note that on some systems, a <1ms timer is not available. In these cases, this test
183 // will not exercise the NtSetTimerResolution path.
184 #[test]
185 #[ignore]
test_nt_timer_works()186 fn test_nt_timer_works() {
187 let _timer_res = enable_high_res_timers_inner(
188 set_time_period,
189 nt_query_timer_resolution,
190 nt_set_timer_resolution,
191 measure_timer_res_1ms,
192 )
193 .unwrap();
194 assert_res_within_bound(measure_timer_resolution())
195 }
196
197 #[test]
198 #[ignore]
test_nt_timer_falls_back_on_failure()199 fn test_nt_timer_falls_back_on_failure() {
200 let _timer_res = enable_high_res_timers_inner(
201 set_time_period,
202 query_timer_res_high_res_available,
203 nt_set_timer_res_broken,
204 measure_timer_res_1ms,
205 )
206 .unwrap();
207 assert_res_within_bound(measure_timer_resolution())
208 }
209
210 #[test]
211 #[ignore]
test_nt_timer_falls_back_on_measurement_failure()212 fn test_nt_timer_falls_back_on_measurement_failure() {
213 let _timer_res = enable_high_res_timers_inner(
214 set_time_period,
215 query_timer_res_high_res_available,
216 nt_set_timer_res_broken,
217 measure_timer_res_2ms,
218 )
219 .unwrap();
220 assert_res_within_bound(measure_timer_resolution())
221 }
222
223 #[test]
224 #[ignore]
test_nt_timer_falls_back_on_low_res_system()225 fn test_nt_timer_falls_back_on_low_res_system() {
226 let _timer_res = enable_high_res_timers_inner(
227 set_time_period,
228 query_timer_res_high_res_unavailable,
229 nt_set_timer_res_broken,
230 measure_timer_res_1ms,
231 )
232 .unwrap();
233 assert_res_within_bound(measure_timer_resolution())
234 }
235
236 #[test]
237 #[ignore]
test_nt_timer_falls_back_on_query_failure()238 fn test_nt_timer_falls_back_on_query_failure() {
239 let _timer_res = enable_high_res_timers_inner(
240 set_time_period,
241 query_timer_failure,
242 nt_set_timer_res_broken,
243 measure_timer_res_1ms,
244 )
245 .unwrap();
246 assert_res_within_bound(measure_timer_resolution())
247 }
248
249 #[test]
test_all_timer_sets_fail()250 fn test_all_timer_sets_fail() {
251 assert!(enable_high_res_timers_inner(
252 time_period_setter_broken,
253 query_timer_failure,
254 nt_set_timer_res_broken,
255 measure_timer_res_1ms,
256 )
257 .is_err());
258 }
259 }
260