win_time_sys/interrupt.rs
1// Copyright (c) 2026 Mike Grier
2//! Interrupt time and unbiased interrupt time, and the clocks that read them ([WT-D-3]).
3//!
4//! Both count 100 ns ticks from the start of this boot, so a point means nothing after a restart.
5//! They differ only in time spent asleep or hibernating, which interrupt time counts and unbiased
6//! interrupt time leaves out. After the machine has slept once they no longer describe the same
7//! instants, so they are separate timelines and their points do not compare.
8//!
9//! Each has two clocks. The plain clock reads the value the kernel publishes into every process,
10//! so it never enters the kernel; it is as fresh as the last system clock tick, which Microsoft's
11//! documentation puts "typically in the range of 0.5 milliseconds to 15.625 milliseconds,
12//! depending on the hardware platform". The precise clock "reads the timer hardware directly",
13//! which the same documentation says "can be slower". What each costs on a given machine is
14//! `WT-1.6`'s probe to measure.
15//!
16//! [WT-D-3]: https://github.com/MikeGrier/windows-threadpool-sys/blob/main/crates/win-time-sys/DESIGN-NOTES.md#wt-d-3
17
18use core::num::NonZeroU64;
19
20use windows_sys::Win32::System::WindowsProgramming::{
21 QueryInterruptTime, QueryInterruptTimePrecise, QueryUnbiasedInterruptTime,
22 QueryUnbiasedInterruptTimePrecise,
23};
24
25use crate::clock::{Clock, Steady};
26use crate::timeline::{HUNDRED_NANOSECOND_TICKS, TimePoint, Timeline};
27
28#[cfg(test)]
29mod tests;
30
31/// Interrupt time: 100 ns ticks since this boot began, including time asleep or hibernating.
32///
33/// Zero is the start of the boot. On a checked ("debug") build of Windows the count starts about 49
34/// days ahead, so that code which mishandles long uptimes fails early; a point is still valid there,
35/// only larger. This is the workspace's one time base
36/// ([DI-D-37](https://github.com/MikeGrier/windows-threadpool-sys/blob/main/crates/durable-ioring/DESIGN-NOTES.md#di-d-37)).
37pub enum InterruptTime {}
38
39impl Timeline for InterruptTime {
40 fn ticks_per_second() -> NonZeroU64 {
41 HUNDRED_NANOSECOND_TICKS
42 }
43}
44
45/// Unbiased interrupt time: interrupt time without the time spent asleep or hibernating.
46///
47/// Its points do not compare with [`InterruptTime`]'s, which share its unit but, after the first
48/// sleep, not its zero:
49///
50/// ```compile_fail,E0308
51/// use win_time_sys::{Clock, InterruptClock, UnbiasedInterruptClock};
52///
53/// let _ = InterruptClock.now() < UnbiasedInterruptClock.now();
54/// ```
55pub enum UnbiasedInterruptTime {}
56
57impl Timeline for UnbiasedInterruptTime {
58 fn ticks_per_second() -> NonZeroU64 {
59 HUNDRED_NANOSECOND_TICKS
60 }
61}
62
63/// [`InterruptTime`] as of the last system clock tick, through `QueryInterruptTime`: the
64/// cheapest reading, which never enters the kernel.
65#[derive(Clone, Copy, Debug, Default, PartialEq, Eq, Hash)]
66pub struct InterruptClock;
67
68impl Clock for InterruptClock {
69 type Timeline = InterruptTime;
70
71 fn now(&self) -> TimePoint<InterruptTime> {
72 let mut ticks = 0;
73 // SAFETY: the out-parameter is a live, writable local for the duration of the call.
74 unsafe { QueryInterruptTime(&mut ticks) };
75 TimePoint::from_ticks(ticks)
76 }
77}
78
79/// [`InterruptTime`] read from the timer hardware, through `QueryInterruptTimePrecise`: finer than
80/// [`InterruptClock`], and its readings compare with it.
81#[derive(Clone, Copy, Debug, Default, PartialEq, Eq, Hash)]
82pub struct PreciseInterruptClock;
83
84impl Clock for PreciseInterruptClock {
85 type Timeline = InterruptTime;
86
87 fn now(&self) -> TimePoint<InterruptTime> {
88 let mut ticks = 0;
89 // SAFETY: the out-parameter is a live, writable local for the duration of the call.
90 unsafe { QueryInterruptTimePrecise(&mut ticks) };
91 TimePoint::from_ticks(ticks)
92 }
93}
94
95/// [`UnbiasedInterruptTime`] as of the last system clock tick, through
96/// `QueryUnbiasedInterruptTime`, which never enters the kernel.
97#[derive(Clone, Copy, Debug, Default, PartialEq, Eq, Hash)]
98pub struct UnbiasedInterruptClock;
99
100impl Clock for UnbiasedInterruptClock {
101 type Timeline = UnbiasedInterruptTime;
102
103 fn now(&self) -> TimePoint<UnbiasedInterruptTime> {
104 let mut ticks = 0;
105 // SAFETY: the out-parameter is a live, writable local for the duration of the call.
106 let succeeded = unsafe { QueryUnbiasedInterruptTime(&mut ticks) };
107 // Documented to fail only for a null pointer, which a reference cannot be.
108 debug_assert_ne!(succeeded, 0, "QueryUnbiasedInterruptTime failed");
109 TimePoint::from_ticks(ticks)
110 }
111}
112
113/// [`UnbiasedInterruptTime`] read from the timer hardware, through
114/// `QueryUnbiasedInterruptTimePrecise`: finer than [`UnbiasedInterruptClock`], and its readings
115/// compare with it.
116#[derive(Clone, Copy, Debug, Default, PartialEq, Eq, Hash)]
117pub struct PreciseUnbiasedInterruptClock;
118
119impl Clock for PreciseUnbiasedInterruptClock {
120 type Timeline = UnbiasedInterruptTime;
121
122 fn now(&self) -> TimePoint<UnbiasedInterruptTime> {
123 let mut ticks = 0;
124 // SAFETY: the out-parameter is a live, writable local for the duration of the call.
125 unsafe { QueryUnbiasedInterruptTimePrecise(&mut ticks) };
126 TimePoint::from_ticks(ticks)
127 }
128}
129
130// Both timelines count up from the start of the boot and are never set, so none of their four
131// clocks goes backwards.
132impl Steady for InterruptClock {}
133impl Steady for PreciseInterruptClock {}
134impl Steady for UnbiasedInterruptClock {}
135impl Steady for PreciseUnbiasedInterruptClock {}