diff options
| author | Matt Mastracci <matthew@mastracci.com> | 2023-05-17 13:59:55 -0600 |
|---|---|---|
| committer | GitHub <noreply@github.com> | 2023-05-17 13:59:55 -0600 |
| commit | ad223362451688c13a4441563210f58bdb046a78 (patch) | |
| tree | 81e041febfdcd83040f3b50bfad247b53a70c3f6 /ext/web/hr_timer_lock.rs | |
| parent | 1541c2ac9b9b4380adeedad10ed87682c0fc6d49 (diff) | |
feat(ext/web): Request higher-resolution timer on Windows if user requests setTimeout w/short delay (#19149)
If a timer is requested with <=100ms resolution, request the high-res
timer. Since the default Windows timer period is 15ms, this means a
100ms timer could fire at 115ms (15% late). We assume that timers longer
than 100ms are a reasonable cutoff here.
The high-res timers on Windows are still limited. Unfortuntely this
means that our shortest duration 4ms timers can still be 25% late, but
without a more complex timer system or spinning on the clock itself,
we're somewhat bounded by the OS' scheduler itself.
Diffstat (limited to 'ext/web/hr_timer_lock.rs')
| -rw-r--r-- | ext/web/hr_timer_lock.rs | 67 |
1 files changed, 67 insertions, 0 deletions
diff --git a/ext/web/hr_timer_lock.rs b/ext/web/hr_timer_lock.rs new file mode 100644 index 000000000..f1f588d6c --- /dev/null +++ b/ext/web/hr_timer_lock.rs @@ -0,0 +1,67 @@ +// Copyright 2018-2023 the Deno authors. All rights reserved. MIT license. +#[cfg(target_os = "windows")] +mod windows { + use std::marker::PhantomData; + use std::sync::atomic::AtomicU32; + + pub(crate) struct HrTimerLock { + pub(super) _unconstructable: PhantomData<()>, + } + + /// Decrease the reference count of the HR timer on drop. + impl Drop for HrTimerLock { + fn drop(&mut self) { + dec_ref(); + } + } + + /// Maintains the HR timer refcount. This should be more than sufficient as 2^32 timers would be + /// an impossible situation, and if it does somehow happen, the worst case is that we'll disable + /// the high-res timer when we shouldn't (and things would eventually return to proper operation). + static TIMER_REFCOUNT: AtomicU32 = AtomicU32::new(0); + + pub(super) fn inc_ref() { + let old = TIMER_REFCOUNT.fetch_add(1, std::sync::atomic::Ordering::SeqCst); + // Overflow/underflow sanity check in debug mode + debug_assert!(old != u32::MAX); + if old == 0 { + lock_hr(); + } + } + + fn dec_ref() { + let old = TIMER_REFCOUNT.fetch_sub(1, std::sync::atomic::Ordering::SeqCst); + // Overflow/underflow sanity check in debug mode + debug_assert!(old != 0); + if old == 1 { + unlock_hr(); + } + } + + /// If the refcount is > 0, we ask Windows for a lower timer period once. While the underlying + /// Windows timeBeginPeriod/timeEndPeriod API can manage its own reference counts, we choose to + /// use it once per process and avoid nesting these calls. + fn lock_hr() { + // SAFETY: We just want to set the timer period here + unsafe { windows_sys::Win32::Media::timeBeginPeriod(1) }; + } + + fn unlock_hr() { + // SAFETY: We just want to set the timer period here + unsafe { windows_sys::Win32::Media::timeEndPeriod(1) }; + } +} + +#[cfg(target_os = "windows")] +pub(crate) fn hr_timer_lock() -> windows::HrTimerLock { + windows::inc_ref(); + windows::HrTimerLock { + _unconstructable: Default::default(), + } +} + +/// No-op on other platforms. +#[cfg(not(target_os = "windows"))] +pub(crate) fn hr_timer_lock() -> (std::marker::PhantomData<()>,) { + (std::marker::PhantomData::default(),) +} |
