Skip to content

Instantly share code, notes, and snippets.

@CryZe
Last active June 14, 2023 08:42
Show Gist options
  • Save CryZe/afb052b82839d36d5bdbe4e88551f4dc to your computer and use it in GitHub Desktop.
Save CryZe/afb052b82839d36d5bdbe4e88551f4dc to your computer and use it in GitHub Desktop.
Notes on Monotonic Clocks

Notes on Monotonic Clocks

We can't use std's Instant as it's insufficiently specified. It neither guarantees "real time" nor does it guarantee measuring "uptime" (the time the OS has been awake rather than suspended), meaning that you can't actually rely on it in practice. In livesplit-core we definitely want real time rather than uptime. Various operating systems are problematic:

Linux, BSD and other Unixes

POSIX intends for CLOCK_MONOTONIC to be real time, but this wasn't correctly implemented in Linux and due to backwards compatibility concerns they were never able to fix it properly. Thus CLOCK_MONOTONIC means uptime on Linux whereas on other Unixes it means real time (the BSD family). They however introduced CLOCK_BOOTTIME in the Linux kernel 2.6.39 which measures real time. So the solution is to use this on all operating systems that are based on the Linux kernel and fall back to CLOCK_MONOTONIC if the kernel is too old and the syscall fails.

macOS and iOS

macOS and iOS actually do the right thing for CLOCK_MONOTONIC but Rust actually doesn't use it on iOS and macOS, so we also need to use our custom implementation for those too, but skip CLOCK_BOOTTIME as that is Linux specific. clock_gettime itself however has only been available since macOS 10.12 (Sierra) and iOS 10 which both got released in September 2016. While there is mach_continuous_time which does the same thing, it got introduced in the same update and is not recommended by the documentation, so it doesn't help with this problem.

Windows

On Windows std's Instant currently measures real time through QueryPerformanceCounter, but we may need to use a custom implementation for it as well in case this ever changes.

Fuchsia

Fuchsia is based on the new Zircon kernel. It has two functions for querying the time:

zx_clock_get: https://fuchsia.dev/fuchsia-src/reference/syscalls/clock_get zx_clock_get_monotonic: https://fuchsia.dev/fuchsia-src/reference/syscalls/clock_get_monotonic

zx_clock_get_monotonic specifically calls out that it does not adjust during sleep which seems to mean that it doesn't count the time the OS is suspended. This is further evidenced by their libc implementation not treating CLOCK_BOOTTIME differently and a bug ticket being linked there: https://cs.opensource.google/fuchsia/fuchsia/+/main:zircon/third_party/ulib/musl/src/time/clock_gettime.c;l=40;drc=35e7a15cb21e16f0705560e5812b7a045d42c8a5

WASI

https://github.com/WebAssembly/WASI/blob/5ab83a68d4eb4f218a898ed03b963b7393caaedc/phases/snapshot/docs.md#variant-cases

WASI seems to underspecify its monotonic a bit, but says that it is defined as a clock measuring real time, making it sound like a compliant implementation should measure the time the OS is suspended as well.

Web

In the web we use performance.now() which they want to specify as being required to keep counting during any sort of suspends (including both the tab and the OS). The browsers currently do however not implement this correctly at all, due to all of the same issues that we are facing here.

Spec Issue: w3c/hr-time#115

Chromium Bug Ticket: https://bugs.chromium.org/p/chromium/issues/detail?id=1206450

Chrome Implementation (the various time_*.cpp): https://github.com/chromium/chromium/tree/d7da0240cae77824d1eda25745c4022757499131/base/time

Firefox Bug Ticket: https://bugzilla.mozilla.org/show_bug.cgi?id=1709767

Firefox Implementation (the various TimeStamp_*.cpp): https://github.com/mozilla/gecko-dev/blob/08c493902519265d570250c8e7ce575c8cd6f5b5/mozglue/misc

WebKit Bug Ticket: https://bugs.webkit.org/show_bug.cgi?id=225610

WebKit Implementation: https://github.com/WebKit/WebKit/blob/79daff42b19103a15340e4005ac90facf1fc46c9/Source/WTF/wtf/CurrentTime.cpp#L268

Final Notes

We therefore need a custom implementation for all "Linux like operating systems" as well as macOS and iOS. The following list has to match libc (and our Cargo.toml): https://github.com/rust-lang/libc/blob/5632705fe1a7858d82609178ba96b13f98f8c2e6/src/unix/mod.rs#L1451-L1454

We however remove emscripten from this list as it's not actually based on the Linux kernel and instead has its own implementation in JavaScript where it actually errors out on CLOCK_BOOTTIME: https://github.com/emscripten-core/emscripten/blob/0321203d3614a97e4042ffa0c19ab770b1f5aa6c/src/library.js#L1419-L1426

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment