Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

performance.now() should jump after sleep/suspend/hibernation #47724

Open
kanongil opened this issue Apr 26, 2023 · 6 comments
Open

performance.now() should jump after sleep/suspend/hibernation #47724

kanongil opened this issue Apr 26, 2023 · 6 comments
Labels
perf_hooks Issues and PRs related to the implementation of the Performance Timing API.

Comments

@kanongil
Copy link
Contributor

kanongil commented Apr 26, 2023

Version

v18.14.2

Platform

Linux qubit 5.15.108-0-lts #1-Alpine SMP Fri, 21 Apr 2023 05:55:14 +0000 x86_64 GNU/Linux

Subsystem

perf_hooks

What steps will reproduce the bug?

In an interactive node session run:

function timeDelta() {
    // The delta should be approximately the same on each invocation (assuming the system time is unchanged).
    return Date.now() - performance.now();
};
const ref = timeDelta();
const before = timeDelta();

Now suspend and resume the machine / VM through whatever means. Then continue in the same node session:

const after = timeDelta();
console.log('BEFORE:', ref - before, 'AFTER:', ref - after);

Observe that the after value is incorrectly missing a number of seconds that matches the time the system was suspended. Eg. from my run:

BEFORE: -0.410888671875
AFTER: -7346.271728515625

How often does it reproduce? Is there a required condition?

100% (on Linux)

What is the expected behavior? Why is that the expected behavior?

performance.now() is expected to jump with the realtime passed time while suspended according to the spec and w3c/hr-time#115.

The mdn/content#4713 issue also goes into detail on how the "Ticking During Sleep" applies to various platforms.

What do you see instead?

No jump during sleep. Eg. by using more suitable clock reference, as proposed implemented in libuv/libuv#1674.

Additional information

The current performance.now() implementation is based on process.hrtime():

function now() {
const hr = process.hrtime();
return (hr[0] * 1000 + hr[1] / 1e6) - timeOrigin;
}

This issue also seems to contain a lot of relevant context around a concrete problem: open-telemetry/opentelemetry-js#852.

@VoltrexKeyva VoltrexKeyva added the perf_hooks Issues and PRs related to the implementation of the Performance Timing API. label Apr 26, 2023
@tniessen
Copy link
Member

performance.now() is expected to jump with the realtime passed time while suspended according to the spec

Are you referring to this part of the spec?

In certain scenarios (e.g. when a tab is backgrounded), the user agent may choose to throttle timers and periodic callbacks run in that context or even freeze them entirely. Any such throttling should not affect the resolution or accuracy of the time returned by the monotonic clock.

After skimming through the spec, w3c/hr-time#115, open-telemetry/opentelemetry-js#852, and mdn/content#4713, I am not quite sure how to interpret this for non-browsers.

@bnoordhuis
Copy link
Member

performance.now() is expected to jump with the realtime passed time while suspended according to the spec

You could perhaps argue it's a quality-of-implementation issue but it doesn't look like a spec conformance issue to me.

The spec says the clock source should be monotonic. The only guarantee with monotonic clocks is that they don't jump back in time but that's all.

Aside: Date.now() - what you use in your example - is based on a real-time clock, not a monotonic clock. Real-time clocks can jump forward and backward in time.

@kanongil
Copy link
Contributor Author

Are you referring to this part of the spec?

In certain scenarios (e.g. when a tab is backgrounded), the user agent may choose to throttle timers and periodic callbacks run in that context or even freeze them entirely. Any such throttling should not affect the resolution or accuracy of the time returned by the monotonic clock.

Yes.

@kanongil
Copy link
Contributor Author

@bnoordhuis Yeah, the expected suspend behaviour is poorly worded as described in w3c/hr-time#115.

The main reason I bring this up, is that I would like a timer with that quality, and I expect that the current behaviour can cause subtle issues, especially on mobile. I have previously had to investigate and workaround this issue in JS code running on buggy Browsers.

It is also possible that the current behaviour is different across the supported platforms, notably for Windows.

Also, the Date.now() usage in the example is just to demonstrate the issue, and I explicitly caveat that it only works if the system time is unmodified during the test.

@kanongil
Copy link
Contributor Author

kanongil commented Apr 27, 2023

The spec also states regarding clocks that they:

attempt to count 1 millisecond of clock time per 1 millisecond of real-world time

This is not currently the case after a suspend.

@tniessen tniessen changed the title performance.now() is not web compatible performance.now() should jump after sleep/suspend/hibernation Apr 27, 2023
@bnoordhuis
Copy link
Member

Not all platforms have easily accessible adjusted-for-suspend clock sources so this may take a while to materialize, if ever.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
perf_hooks Issues and PRs related to the implementation of the Performance Timing API.
Projects
None yet
Development

No branches or pull requests

4 participants