Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
google
GitHub Repository: google/crosvm
Path: blob/main/base/src/sys/windows/platform_timer_resolution.rs
5394 views
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 super::platform_timer_utils::measure_timer_resolution;
8
use super::platform_timer_utils::nt_query_timer_resolution;
9
use super::platform_timer_utils::nt_set_timer_resolution;
10
use super::platform_timer_utils::set_time_period;
11
use crate::info;
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 {
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 {
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 {
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.
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
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
147
fn time_period_setter_broken(_d: Duration, _b: bool) -> Result<()> {
148
Err(Error::new(100))
149
}
150
151
fn query_timer_failure() -> Result<(Duration, Duration)> {
152
Err(Error::new(100))
153
}
154
155
fn query_timer_res_high_res_available() -> Result<(Duration, Duration)> {
156
Ok((Duration::from_millis(15), Duration::from_micros(500)))
157
}
158
159
fn query_timer_res_high_res_unavailable() -> Result<(Duration, Duration)> {
160
Ok((Duration::from_millis(15), Duration::from_millis(1)))
161
}
162
163
fn nt_set_timer_res_broken(_: Duration) -> Result<()> {
164
Err(Error::new(100))
165
}
166
fn measure_timer_res_1ms() -> Duration {
167
Duration::from_millis(1)
168
}
169
170
fn measure_timer_res_2ms() -> Duration {
171
Duration::from_millis(2)
172
}
173
174
fn assert_res_within_bound(actual_res: Duration) {
175
assert!(
176
actual_res <= Duration::from_millis(2),
177
"actual_res was {actual_res:?}, expected <= 2ms"
178
);
179
}
180
181
// Note that on some systems, a <1ms timer is not available. In these cases, this test
182
// will not exercise the NtSetTimerResolution path.
183
#[test]
184
#[ignore]
185
fn test_nt_timer_works() {
186
let _timer_res = enable_high_res_timers_inner(
187
set_time_period,
188
nt_query_timer_resolution,
189
nt_set_timer_resolution,
190
measure_timer_res_1ms,
191
)
192
.unwrap();
193
assert_res_within_bound(measure_timer_resolution())
194
}
195
196
#[test]
197
#[ignore]
198
fn test_nt_timer_falls_back_on_failure() {
199
let _timer_res = enable_high_res_timers_inner(
200
set_time_period,
201
query_timer_res_high_res_available,
202
nt_set_timer_res_broken,
203
measure_timer_res_1ms,
204
)
205
.unwrap();
206
assert_res_within_bound(measure_timer_resolution())
207
}
208
209
#[test]
210
#[ignore]
211
fn test_nt_timer_falls_back_on_measurement_failure() {
212
let _timer_res = enable_high_res_timers_inner(
213
set_time_period,
214
query_timer_res_high_res_available,
215
nt_set_timer_res_broken,
216
measure_timer_res_2ms,
217
)
218
.unwrap();
219
assert_res_within_bound(measure_timer_resolution())
220
}
221
222
#[test]
223
#[ignore]
224
fn test_nt_timer_falls_back_on_low_res_system() {
225
let _timer_res = enable_high_res_timers_inner(
226
set_time_period,
227
query_timer_res_high_res_unavailable,
228
nt_set_timer_res_broken,
229
measure_timer_res_1ms,
230
)
231
.unwrap();
232
assert_res_within_bound(measure_timer_resolution())
233
}
234
235
#[test]
236
#[ignore]
237
fn test_nt_timer_falls_back_on_query_failure() {
238
let _timer_res = enable_high_res_timers_inner(
239
set_time_period,
240
query_timer_failure,
241
nt_set_timer_res_broken,
242
measure_timer_res_1ms,
243
)
244
.unwrap();
245
assert_res_within_bound(measure_timer_resolution())
246
}
247
248
#[test]
249
fn test_all_timer_sets_fail() {
250
assert!(enable_high_res_timers_inner(
251
time_period_setter_broken,
252
query_timer_failure,
253
nt_set_timer_res_broken,
254
measure_timer_res_1ms,
255
)
256
.is_err());
257
}
258
}
259
260