aboutsummaryrefslogtreecommitdiffstats
path: root/components/shared/base/cross_process_instant.rs
diff options
context:
space:
mode:
Diffstat (limited to 'components/shared/base/cross_process_instant.rs')
-rw-r--r--components/shared/base/cross_process_instant.rs167
1 files changed, 167 insertions, 0 deletions
diff --git a/components/shared/base/cross_process_instant.rs b/components/shared/base/cross_process_instant.rs
new file mode 100644
index 00000000000..aa504b8b389
--- /dev/null
+++ b/components/shared/base/cross_process_instant.rs
@@ -0,0 +1,167 @@
+// Copyright 2024 The Servo Project Developers. See the COPYRIGHT
+// file at the top-level directory of this distribution and at
+// http://rust-lang.org/COPYRIGHT.
+//
+// Licensed under the Apache License, Version 2.0 <LICENSE-APACHE or
+// http://www.apache.org/licenses/LICENSE-2.0> or the MIT license
+// <LICENSE-MIT or http://opensource.org/licenses/MIT>, at your
+// option. This file may not be copied, modified, or distributed
+// except according to those terms.
+
+//! An implementation of a monotonic, nanosecond precision timer, like [`std::time::Instant`] that
+//! can be serialized and compared across processes.
+
+use std::ops::{Add, Sub};
+
+use malloc_size_of_derive::MallocSizeOf;
+use serde::{Deserialize, Serialize};
+use time_03::Duration;
+
+/// A monotonic, nanosecond precision timer that can be used cross-process. The value
+/// stored internally is purposefully opaque as the origin is platform-specific. They can
+/// be compared and [`time_03::Duration`] can be found by subtracting one from another.
+/// The `time` crate is used in this case instead of `std::time` so that durations can
+/// be negative.
+#[derive(
+ Clone, Copy, Debug, Deserialize, Eq, MallocSizeOf, Ord, PartialEq, PartialOrd, Serialize,
+)]
+pub struct CrossProcessInstant {
+ value: u64,
+}
+
+impl CrossProcessInstant {
+ pub fn now() -> Self {
+ Self {
+ value: platform::now(),
+ }
+ }
+
+ /// Some unspecified time epoch. This is mainly useful for converting DOM's `timeOrigin` into a
+ /// `DOMHighResolutionTimestamp`. See <https://w3c.github.io/hr-time/#sec-time-origin>.
+ pub fn epoch() -> Self {
+ Self { value: 0 }
+ }
+}
+
+impl Sub for CrossProcessInstant {
+ type Output = Duration;
+
+ fn sub(self, rhs: Self) -> Self::Output {
+ Duration::nanoseconds(self.value as i64 - rhs.value as i64)
+ }
+}
+
+impl Add<Duration> for CrossProcessInstant {
+ type Output = Self;
+
+ fn add(self, rhs: Duration) -> Self::Output {
+ Self {
+ value: self.value + rhs.whole_nanoseconds() as u64,
+ }
+ }
+}
+
+#[cfg(all(unix, not(any(target_os = "macos", target_os = "ios"))))]
+mod platform {
+ use libc::timespec;
+
+ #[allow(unsafe_code)]
+ pub(super) fn now() -> u64 {
+ // SAFETY: libc::timespec is zero initializable.
+ let time = unsafe {
+ let mut time: timespec = std::mem::zeroed();
+ libc::clock_gettime(libc::CLOCK_MONOTONIC, &mut time);
+ time
+ };
+ (time.tv_sec as u64) * 1000000000 + (time.tv_nsec as u64)
+ }
+}
+
+#[cfg(any(target_os = "macos", target_os = "ios"))]
+mod platform {
+ use std::sync::LazyLock;
+
+ use mach2::mach_time::{mach_absolute_time, mach_timebase_info};
+
+ #[allow(unsafe_code)]
+ fn timebase_info() -> &'static mach_timebase_info {
+ static TIMEBASE_INFO: LazyLock<mach_timebase_info> = LazyLock::new(|| {
+ let mut timebase_info = mach_timebase_info { numer: 0, denom: 0 };
+ unsafe { mach_timebase_info(&mut timebase_info) };
+ timebase_info
+ });
+ &*TIMEBASE_INFO
+ }
+
+ #[allow(unsafe_code)]
+ pub(super) fn now() -> u64 {
+ let timebase_info = timebase_info();
+ let absolute_time = unsafe { mach_absolute_time() };
+ absolute_time * timebase_info.numer as u64 / timebase_info.denom as u64
+ }
+}
+
+#[cfg(target_os = "windows")]
+mod platform {
+ use std::sync::atomic::{AtomicU64, Ordering};
+
+ use windows_sys::Win32::System::Performance::{
+ QueryPerformanceCounter, QueryPerformanceFrequency,
+ };
+
+ /// The frequency of the value returned by `QueryPerformanceCounter` in counts per
+ /// second. This is taken from the Rust source code at:
+ /// <https://github.com/rust-lang/rust/blob/1a1cc050d8efc906ede39f444936ade1fdc9c6cb/library/std/src/sys/pal/windows/time.rs#L197>
+ #[allow(unsafe_code)]
+ fn frequency() -> i64 {
+ // Either the cached result of `QueryPerformanceFrequency` or `0` for
+ // uninitialized. Storing this as a single `AtomicU64` allows us to use
+ // `Relaxed` operations, as we are only interested in the effects on a
+ // single memory location.
+ static FREQUENCY: AtomicU64 = AtomicU64::new(0);
+
+ let cached = FREQUENCY.load(Ordering::Relaxed);
+ // If a previous thread has filled in this global state, use that.
+ if cached != 0 {
+ return cached as i64;
+ }
+ // ... otherwise learn for ourselves ...
+ let mut frequency = 0;
+ let result = unsafe { QueryPerformanceFrequency(&mut frequency) };
+
+ if result == 0 {
+ return 0;
+ }
+
+ FREQUENCY.store(frequency as u64, Ordering::Relaxed);
+ frequency
+ }
+
+ #[allow(unsafe_code)]
+ /// Get the current instant value in nanoseconds.
+ /// Originally from: <https://github.com/rust-lang/rust/blob/1a1cc050d8efc906ede39f444936ade1fdc9c6cb/library/std/src/sys/pal/windows/time.rs#L175>
+ pub(super) fn now() -> u64 {
+ let mut counter_value = 0;
+ unsafe { QueryPerformanceCounter(&mut counter_value) };
+
+ /// Computes (value*numer)/denom without overflow, as long as both
+ /// (numer*denom) and the overall result fit into i64 (which is the case
+ /// for our time conversions).
+ /// Originally from: <https://github.com/rust-lang/rust/blob/1a1cc050d8efc906ede39f444936ade1fdc9c6cb/library/std/src/sys_common/mod.rs#L75>
+ fn mul_div_u64(value: u64, numer: u64, denom: u64) -> u64 {
+ let q = value / denom;
+ let r = value % denom;
+ // Decompose value as (value/denom*denom + value%denom),
+ // substitute into (value*numer)/denom and simplify.
+ // r < denom, so (denom*numer) is the upper bound of (r*numer)
+ q * numer + r * numer / denom
+ }
+
+ static NANOSECONDS_PER_SECOND: u64 = 1_000_000_000;
+ mul_div_u64(
+ counter_value as u64,
+ NANOSECONDS_PER_SECOND,
+ frequency() as u64,
+ )
+ }
+}