Skip to main content

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 {}