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

perf_hooks: fix timerify bug #42883

Open
wants to merge 4 commits into
base: main
Choose a base branch
from

Conversation

iam-frankqiu
Copy link
Contributor

@iam-frankqiu iam-frankqiu commented Apr 27, 2022

Fixes: #42743

@nodejs-github-bot nodejs-github-bot added the needs-ci PRs that need a full CI run. label Apr 27, 2022
@himself65
Copy link
Member

I've fixed the issue

@himself65 himself65 added the request-ci Add this label to start a Jenkins CI on a PR. label Apr 28, 2022
@github-actions github-actions bot removed the request-ci Add this label to start a Jenkins CI on a PR. label Apr 28, 2022
@nodejs-github-bot
Copy link
Collaborator

@himself65
Copy link
Member

could you please add a test case for this?

lib/internal/perf/timerify.js Outdated Show resolved Hide resolved
if (!constructor && typeof result?.finally === 'function') {
return result.finally(
if (!constructor && typeof result?.then === 'function') {
return result.then(
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Here we retrieve result.then twice which theoretically not work like JS spec strictly. We'd better write a helper to ensure such subtle semantic. (Not sure whether Node.js already have such internal thenable helpers).

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

What do you mean?

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Do you mean like this?

node/lib/assert.js

Lines 763 to 771 in 1e76165

function checkIsPromise(obj) {
// Accept native ES6 promises and promises that are implemented in a similar
// way. Do not accept thenables that use a function as `obj` and that have no
// `catch` handler.
return isPromise(obj) ||
(obj !== null && typeof obj === 'object' &&
typeof obj.then === 'function' &&
typeof obj.catch === 'function');
}

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I mean, if follow JS spec strictly, the code should be:

const {then} = result
if (typeof then === 'function') {
  ReflectApply(then, result, [callback])
}

Because theoretically result.then could be a getter, and two retrieve could give u different result.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I also don't understand why checkIsPromise "do not accept thenables that use a function as obj and that have no catch handler" intentionally, this limitation seems wrong to me.

lib/internal/perf/timerify.js Outdated Show resolved Hide resolved
@himself65 himself65 force-pushed the fix_timerify_bug branch from 9dc34fa to da9e49a Compare May 1, 2022 20:15
Copy link
Member

@himself65 himself65 left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

also, we need to edit the document

node/doc/api/perf_hooks.md

Lines 336 to 338 in 7a53696

If the wrapped function returns a promise, a finally handler will be attached
to the promise and the duration will be reported once the finally handler is
invoked.

@iam-frankqiu iam-frankqiu requested a review from himself65 May 4, 2022 12:05
if (!constructor && typeof result?.then === 'function') {
return result.then(
function wrappedTimerifiedPromise(value) {
processComplete(fn.name, start, args, histogram);
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

One idea which is not relate to the bug directly, but:

currently, now is called in the processComplete (duration = now() - start) , but consider the goal is to calc the time as precise as possible, it may be better to calc it outside like:

const t = now()
processComplete(fn.name, start - t, args, histogram)

(also need to modify other related places, omit here)

This could rule out the invoking time of processComplete, in the cases of microbenchmark, such cost can't be ignored.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
needs-ci PRs that need a full CI run.
Projects
None yet
Development

Successfully merging this pull request may close these issues.

performance.timerify(fn) behave inconsistently for sync/async functions
4 participants