Bug 225610 - performance.now() does not tick during system sleep
Summary: performance.now() does not tick during system sleep
Status: NEW
Alias: None
Product: WebKit
Classification: Unclassified
Component: Platform (show other bugs)
Version: Safari 14
Hardware: Mac (Intel) macOS 11
: P2 Normal
Assignee: Nobody
URL:
Keywords: InRadar
Depends on:
Blocks:
 
Reported: 2021-05-10 13:44 PDT by Jonathan Mayer
Modified: 2023-09-19 05:21 PDT (History)
5 users (show)

See Also:


Attachments

Note You need to log in before you can comment on or make changes to this bug.
Description Jonathan Mayer 2021-05-10 13:44:52 PDT
Steps to Reproduce
------------------
1. Open a tab and navigate to a page. Open the console and compute the difference between the system clock and the document's monotonic clock (Date.now() -  performance.timing.navigationStart - performance.now()).
2. Put the system to sleep for a period of time, then wake the system.
3. For the same page, recompute the difference between the system clock and the document's monotonic clock.

Expected Result
---------------
The difference between the system clock and the document's monotonic clock should be about the same before and after sleep, because the monotonic clock continued to (logically) tick during sleep.

The original W3C High Resolution Time spec, which WebKit fully implements, did not address monotonic clock behavior during sleep. The newer Level 2 spec, which WebKit partially implements, indicates that monotonic clocks should continue to tick during sleep. See: https://github.com/mdn/content/issues/4713

Actual Result
-------------
The difference between the system clock and the document's monotonic clock is approximately the duration of the system sleep, because the monotonic clock froze during sleep.

The cause of the issue appears to be the choice of operating system clock API:
https://github.com/WebKit/WebKit/blob/79daff42b19103a15340e4005ac90facf1fc46c9/Source/WebCore/page/Performance.cpp#L75
https://github.com/WebKit/WebKit/blob/79daff42b19103a15340e4005ac90facf1fc46c9/Source/WTF/wtf/CurrentTime.cpp#L268

MonotonicTime::now() currently uses the following clocks:
* GLib: g_get_monotonic_time(), which may or may not tick during sleep (either is specifically permitted), and which may or may not be subject to clock adjustments that are monotonic and small/gradual (unspecified).
* Darwin: mach_absolute_time(), which does not tick during sleep, and which is not subject to any clock adjustments.
* Fuchsia: zx_clock_get_monotonic(), which does not tick during sleep (current behavior but documentation notes this is unsettled), and which may or may not be subject to clock adjustments that are monotonic and small/gradual (unspecified).
* Linux: clock_gettime() with CLOCK_MONOTONIC, which does not tick during sleep, and which may be subject to clock adjustments that are monotonic and small/gradual.
* BSD: clock_gettime() with CLOCK_MONOTONIC, which does tick during sleep, and which may be subject to clock adjustments that are monotonic and small/gradual.

The right clocks, at least for performance.now() purposes, probably are:
* GLib: there is no clock that ticks during sleep, so fall back to the operating system clock.
* Darwin: clock_gettime() with CLOCK_MONOTONIC (same sleep and adjustment behavior as BSD above).
* Fuchsia: there is no clock that ticks during sleep... zx_clock_get_monotonic() is the only option.
* Linux: clock_gettime() with CLOCK_BOOTTIME (same as BSD above).
* BSD: same as above.
Comment 1 Alexey Proskuryakov 2021-05-10 14:31:46 PDT
Thank you for the super detailed report!

Seeing that most browsers are not compliant with this spec change, I'm wondering what the rationale for changing established behavior is. Comparing a monotonic clock to system clock will break once a year in most locations anyway, as the system clock will go backwards.
Comment 2 Jonathan Mayer 2021-05-10 17:04:24 PDT
(In reply to Alexey Proskuryakov from comment #1)
> Thank you for the super detailed report!
> 
> Seeing that most browsers are not compliant with this spec change, I'm wondering what the rationale for changing established behavior is.

Here's the W3C spec discussion: https://github.com/w3c/hr-time/issues/65

A few reasons for consideration:
* Consistency with API user expectations, especially since the widely used Date.now() ticks during system sleep.
* Consistency across browsers and platforms.
* Provides valuable semantics for performance.timeOrigin + performance.now(). Those timestamps will be, within a browsing session: 1) monotonic, 2) comparable across documents, 3) unaffected by major or non-monotonic adjustments to the system clock, and 4) approximately equal to the current time, assuming the system clock was accurate at browser startup. The reason I started digging into this issue is that my academic research group is relying on those very semantics for a project. :)

> Comparing a monotonic clock to system clock will break once a year in most locations anyway, as the system clock will go backwards.

If you mean leap seconds, I think that's OK. There's no guarantee of how close the monotonic clock and the system clock will be at any particular moment, since the system clock can change at any time. What a browser can guarantee is: 1) the monotonic clock value is approximately equal to the  value the system clock would have if it wasn't subject to a major or non-monotonic adjustment after browser startup, and 2) the only other causes of divergence between the monotonic clock and the system clock are a) underlying clock implementation differences (unlikely now, with convergence on invariant TSC for x86 timing and Generic Timers for ARM timing), and b) leap second subtraction or smearing on the system clock (which an API user could account for if necessary by implementing their own leap second subtraction or smearing).
Comment 3 Alexey Proskuryakov 2021-05-11 12:04:05 PDT
I was talking about daylight savings time adjustment. Perhaps you are considering system clock adjusted for that.
Comment 4 Jonathan Mayer 2021-05-11 13:54:14 PDT
(In reply to Alexey Proskuryakov from comment #3)
> I was talking about daylight savings time adjustment. Perhaps you are
> considering system clock adjusted for that.

I don't think daylight savings time is an issue, because the system clock (Date.now()) is defined in UTC and UTC doesn't have DST. DST doesn't actually adjust the system clock; it changes the mapping from the system clock to local time (like a time zone). The same mapping can be applied to the monotonic clock if helpful for a use case (new Date(performance.timeOrigin + performance.now())).
Comment 5 Radar WebKit Bug Importer 2021-05-17 13:45:17 PDT
<rdar://problem/78120007>
Comment 6 Alex Christensen 2021-05-21 15:50:23 PDT
I was looking into something else and found something related to this problem: Performance::now calls MonotonicTime::now which calls mach_absolute_time.  If I understand correctly, this is a request for a timer that uses mach_continuous_time instead.
Comment 7 Jonathan Mayer 2021-05-21 18:13:15 PDT
(In reply to Alex Christensen from comment #6)
> I was looking into something else and found something related to this
> problem: Performance::now calls MonotonicTime::now which calls
> mach_absolute_time.  If I understand correctly, this is a request for a
> timer that uses mach_continuous_time instead.

Using clock_gettime / clock_gettime_nsec_np with CLOCK_MONOTONIC might be preferable to CLOCK_MONOTONIC_RAW (which is equivalent to mach_continuous_time). CLOCK_MONOTONIC allows for small monotonic adjustments (e.g., NTP oscillator corrections) and CLOCK_MONOTONIC_RAW doesn't. The result can be an unexpected drift from the system clock. Chrome currently has this very issue (with mach_absolute_time), and in a quick check, I also saw clock drift in Safari on macOS.

https://bugs.chromium.org/p/chromium/issues/detail?id=948384