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

errors: improve hideStackFrames #49990

Merged
merged 1 commit into from
Nov 11, 2023

Conversation

Uzlopak
Copy link
Contributor

@Uzlopak Uzlopak commented Oct 1, 2023

This PR improves the hideStackFrames logic.

hideStackFrames is used to hide function calls from stracktraces of Errors. E.g. we call Buffer.alloc(size), then we check if the parameter size is an integer and call validateNumber(size, 'size', 0, kMaxLength);.

'use strict'

try {
    Buffer.alloc('1')
} catch (err) {
    console.error(err.stack)
}

then we get the following stacktrace:

TypeError [ERR_INVALID_ARG_TYPE]: The "size" argument must be of type number. Received type string ('1')
    at Function.alloc (node:buffer:393:3)
    at Object.<anonymous> (/home/aras/workspace/node/test.js:4:12)
    at Module._compile (node:internal/modules/cjs/loader:1369:14)
    at Module._extensions..js (node:internal/modules/cjs/loader:1427:10)
    at Module.load (node:internal/modules/cjs/loader:1201:32)
    at Module._load (node:internal/modules/cjs/loader:1017:12)
    at Function.executeUserEntryPoint [as runMain] (node:internal/modules/run_main:104:12)
    at node:internal/main/run_main_module:23:47

If we remove the hideStackFrame wrapping for demonstration purposes we get:

TypeError [ERR_INVALID_ARG_TYPE]: The "size" argument must be of type number. Received type string ('1')
    at validateNumber (node:internal/validators:177:11)
    at Function.alloc (node:buffer:393:3)
    at Object.<anonymous> (/home/aras/workspace/node/test.js:4:12)
    at Module._compile (node:internal/modules/cjs/loader:1369:14)
    at Module._extensions..js (node:internal/modules/cjs/loader:1427:10)
    at Module.load (node:internal/modules/cjs/loader:1201:32)
    at Module._load (node:internal/modules/cjs/loader:1017:12)
    at Function.executeUserEntryPoint [as runMain] (node:internal/modules/run_main:104:12)
    at node:internal/main/run_main_module:23:47

So we have the additional at validateNumber (node:internal/validators:177:11) entry.

We want to hide this frame, as it is some node internal information, and does not give any benefit for the developer.

Javascript has no inline specifier, so we have to hide it differently. In main branch this is solved by prefixing the function name with __node_internal_ and then generating the whole stacktrace with captureLargerStackTrace, which sets Error.stackTraceLimit = Infinity. When we then call Error.stack, prepareStackTrace will filter out the all frames, which start with __node_internal_.

But we dont need the whole stackTrace. We only need a limited number, by default 10 frames.

To achieve this we just generate the necessary stackTrace.

So hideStackFrames will wrap the original function and will capture the correct stack trace if an error is thrown. Functions which are wrapped have to call the HideStackFramesError Errors and wont have a Stack trace.

An edge case is when a function, which uses hideStackFrames calls another function which is wrapped with hideStackFrames. To avoid double generation of the stack, a function which is wrapped with hideStackFrames exposes a function as attribute of the wrapped function called .withoutStackTrace. The outer function will then get the necessary stack trace.

Usually double wrapped functions are validators. I would have liked to extract them all into validators.js or into module/validators.js. But this would have reduced the readability further.

@nodejs-github-bot
Copy link
Collaborator

Review requested:

  • @nodejs/http
  • @nodejs/http2
  • @nodejs/net

@nodejs-github-bot nodejs-github-bot added lib / src Issues and PRs related to general changes in the lib or src directory. needs-ci PRs that need a full CI run. labels Oct 1, 2023
@mcollina
Copy link
Member

mcollina commented Oct 1, 2023

what's the benefit/performance diff?

@Uzlopak
Copy link
Contributor Author

Uzlopak commented Oct 1, 2023

My benchmarks dont show regressions.
But if we go on, I would ensure that all touched code does not contain any perf regressions.

The benefit is, that the stack trace will be generated propery, without the need to call captureLargerStackTrace. So we wont be forced to keep infinity amount of CallSites just to kick some.

On the long run, i want to try to remove prepareStackTrace.

Currently i investigate what this overrideStackTrace Map is doing, and how we can avoid it.

@@ -372,6 +356,23 @@ function makeSystemErrorWithCode(key) {
};
}

function makeNodeErrorForHideStackFrame(Base) {
class HideStackFramesError extends Base {
Copy link
Member

@benjamingr benjamingr Oct 1, 2023

Choose a reason for hiding this comment

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

Won't this mess with the .name of the error and how stack traces look (vs the previous implementation)?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

The only reason i created this class, was because to avoid to patch everywhere the logic for Error.stackTraceLimit over and over.

The name should be the same imho. But i can tackle it.

Copy link
Member

Choose a reason for hiding this comment

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

Thanks let me know when ready

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Does this test cover the issue?

// Test that validator errors have the correct name and message.

@Uzlopak
Copy link
Contributor Author

Uzlopak commented Oct 2, 2023

The existing tests pass.

Next step is to remove the captureLargerStackTrace function and ensure that the logic is equivalent.

Imho this is an exciting PR for me. If done right, this should improve the performance of basically every error case. And tbh it seems to be a smart solution (patting on my own shoulder).

Also it will cover all the validateX functions.so it should expose less unnecessary stacks.

@Uzlopak
Copy link
Contributor Author

Uzlopak commented Oct 2, 2023

@Uzlopak Uzlopak force-pushed the hidestack-frames branch 4 times, most recently from a2134fc to 3f94be4 Compare October 4, 2023 20:11
@Uzlopak
Copy link
Contributor Author

Uzlopak commented Oct 4, 2023

@anonrig can you please add the author-ready tag?

I would like to discuss this.

@anonrig
Copy link
Member

anonrig commented Oct 4, 2023

@anonrig can you please add the author-ready tag?

author-ready requires at least 1 collaborator approval. I don't have enough knowledge to review this. I recommend @benjamingr or @mcollina to leave a review before adding author-ready

@anonrig anonrig added the request-ci Add this label to start a Jenkins CI on a PR. label Oct 4, 2023
@github-actions github-actions bot removed the request-ci Add this label to start a Jenkins CI on a PR. label Oct 4, 2023
@nodejs-github-bot
Copy link
Collaborator

@tniessen tniessen added the review wanted PRs that need reviews. label Oct 4, 2023
@targos
Copy link
Member

targos commented Oct 5, 2023

Also @BridgeAR

@Uzlopak Uzlopak force-pushed the hidestack-frames branch 3 times, most recently from da8ff55 to 134ea2b Compare October 5, 2023 12:05
Copy link
Member

@mcollina mcollina left a comment

Choose a reason for hiding this comment

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

lgtm

lib/internal/errors.js Outdated Show resolved Hide resolved
@mcollina
Copy link
Member

You are correct

});
if (otherClasses.includes(HideStackFramesError)) {
if (otherClasses.length !== 1) {
otherClasses.forEach((clazz) => {
Copy link
Contributor Author

Choose a reason for hiding this comment

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

@mcollina
@anonrig

Should we use a for loop instead?

otherClasses.forEach((clazz) => {
def[clazz.name] = makeNodeErrorWithCode(clazz, sym);
});
if (otherClasses.includes(HideStackFramesError)) {
Copy link
Contributor Author

Choose a reason for hiding this comment

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

@mcollina
@anonrig

Should we use primordials?

Copy link
Member

Choose a reason for hiding this comment

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

I would recommend we do.

@Uzlopak
Copy link
Contributor Author

Uzlopak commented Oct 14, 2023

@benjamingr
Can you have a look?

Copy link
Member

@benjamingr benjamingr left a comment

Choose a reason for hiding this comment

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

Don't have capacity, sorry, unblocking so others can progress

@Uzlopak
Copy link
Contributor Author

Uzlopak commented Oct 15, 2023

@anonrig
Can you now add the author-ready tag, please?

@anonrig anonrig added author ready PRs that have at least one approval, no pending requests for changes, and a CI started. request-ci Add this label to start a Jenkins CI on a PR. labels Oct 15, 2023
@github-actions github-actions bot removed the request-ci Add this label to start a Jenkins CI on a PR. label Oct 15, 2023
@nodejs-github-bot
Copy link
Collaborator

@Uzlopak
Copy link
Contributor Author

Uzlopak commented Oct 15, 2023

@anonrig
can you retrigger the failed CI,please?

@nodejs-github-bot
Copy link
Collaborator

@nodejs-github-bot
Copy link
Collaborator

@nodejs-github-bot
Copy link
Collaborator

nodejs-github-bot commented Nov 2, 2023

Copy link
Member

@mcollina mcollina left a comment

Choose a reason for hiding this comment

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

lgtm

@jasnell jasnell added the commit-queue Add this label to land a pull request using GitHub Actions. label Nov 11, 2023
@nodejs-github-bot nodejs-github-bot removed the commit-queue Add this label to land a pull request using GitHub Actions. label Nov 11, 2023
@nodejs-github-bot nodejs-github-bot merged commit 83e6350 into nodejs:main Nov 11, 2023
57 checks passed
@nodejs-github-bot
Copy link
Collaborator

Landed in 83e6350

targos pushed a commit that referenced this pull request Nov 11, 2023
PR-URL: #49990
Reviewed-By: Matteo Collina <[email protected]>
Reviewed-By: Yagiz Nizipli <[email protected]>
Reviewed-By: James M Snell <[email protected]>
@Uzlopak Uzlopak deleted the hidestack-frames branch November 13, 2023 07:42
targos pushed a commit that referenced this pull request Nov 14, 2023
PR-URL: #49990
Reviewed-By: Matteo Collina <[email protected]>
Reviewed-By: Yagiz Nizipli <[email protected]>
Reviewed-By: James M Snell <[email protected]>
richardlau pushed a commit that referenced this pull request Mar 25, 2024
PR-URL: #49990
Reviewed-By: Matteo Collina <[email protected]>
Reviewed-By: Yagiz Nizipli <[email protected]>
Reviewed-By: James M Snell <[email protected]>
@richardlau richardlau mentioned this pull request Mar 25, 2024
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
author ready PRs that have at least one approval, no pending requests for changes, and a CI started. lib / src Issues and PRs related to general changes in the lib or src directory. needs-ci PRs that need a full CI run. needs-citgm PRs that need a CITGM CI run. review wanted PRs that need reviews.
Projects
None yet
Development

Successfully merging this pull request may close these issues.

10 participants