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

lib: improve error creation performance #24747

Closed
wants to merge 2 commits into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
12 changes: 12 additions & 0 deletions lib/_http_outgoing.js
Original file line number Diff line number Diff line change
Expand Up @@ -445,20 +445,32 @@ function matchHeader(self, state, field, value) {

function validateHeaderName(name) {
if (typeof name !== 'string' || !name || !checkIsHttpToken(name)) {
// Reducing the limit improves the performance significantly. We do not
// lose the stack frames due to the `captureStackTrace()` function that is
// called later.
const tmpLimit = Error.stackTraceLimit;
Error.stackTraceLimit = 0;
const err = new ERR_INVALID_HTTP_TOKEN('Header name', name);
Error.stackTraceLimit = tmpLimit;
Error.captureStackTrace(err, validateHeaderName);
throw err;
}
}

function validateHeaderValue(name, value) {
let err;
// Reducing the limit improves the performance significantly. We do not loose
// the stack frames due to the `captureStackTrace()` function that is called
// later.
const tmpLimit = Error.stackTraceLimit;
Error.stackTraceLimit = 0;
if (value === undefined) {
err = new ERR_HTTP_INVALID_HEADER_VALUE(value, name);
} else if (checkInvalidHeaderChar(value)) {
debug('Header "%s" contains invalid characters', name);
err = new ERR_INVALID_CHAR('header content', name);
}
Error.stackTraceLimit = tmpLimit;
if (err !== undefined) {
Error.captureStackTrace(err, validateHeaderValue);
throw err;
Expand Down
24 changes: 24 additions & 0 deletions lib/internal/errors.js
Original file line number Diff line number Diff line change
Expand Up @@ -261,10 +261,16 @@ function uvException(ctx) {
message += ` -> '${dest}'`;
}

// Reducing the limit improves the performance significantly. We do not loose
// the stack frames due to the `captureStackTrace()` function that is called
// later.
const tmpLimit = Error.stackTraceLimit;
Error.stackTraceLimit = 0;
// Pass the message to the constructor instead of setting it on the object
// to make sure it is the same as the one created in C++
// eslint-disable-next-line no-restricted-syntax
const err = new Error(message);
Error.stackTraceLimit = tmpLimit;
Copy link
Member

@jdalton jdalton Nov 30, 2018

Choose a reason for hiding this comment

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

This seems like something V8 folks should be made aware of since, in this case, the lazily populated .stack isn't even being accessed yet. The fact that setting the Error.stackTraceLimit to 0 appears to improves basic error creation performance when the .stack isn't accessed is counterintuitive at least.

I'd rather this be tracked as a V8 issue and fixed there than throwing Error.stackTraceLimit = 0 all over the Node codebase.

Copy link
Member Author

Choose a reason for hiding this comment

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

As mentioned above, I already did that :)

Copy link
Member

Choose a reason for hiding this comment

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

Yes, but the V8 issues mentioned above

  • 8451 – loading Error.stackTraceLimit could be done faster:

    We load Error.stackTraceLimit every time we collect a stack trace. Maybe this is something we want to improve? See https://docs.google.com/document/d/1wwigvXVQ-mdLmSfNnLie2xVQGCt-6AKhP1OuyE38Vw8/edit

  • 8452 – Faster stack traces:

    Stack traces are not used a lot in benchmarks, but often on real web sites for telemetry purposes. Some Node.js frameworks also use it to implement async stack traces. We could use better performance for stack trace collection in general

don't clearly state this specific case though.

Copy link
Member Author

Choose a reason for hiding this comment

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

I opened another issue to properly address this: https://bugs.chromium.org/p/v8/issues/detail?id=8555


for (const prop of Object.keys(ctx)) {
if (prop === 'message' || prop === 'path' || prop === 'dest') {
Expand Down Expand Up @@ -307,8 +313,14 @@ function uvExceptionWithHostPort(err, syscall, address, port) {
details = ` ${address}`;
}

// Reducing the limit improves the performance significantly. We do not loose
// the stack frames due to the `captureStackTrace()` function that is called
// later.
const tmpLimit = Error.stackTraceLimit;
Error.stackTraceLimit = 0;
// eslint-disable-next-line no-restricted-syntax
const ex = new Error(`${message}${details}`);
Error.stackTraceLimit = tmpLimit;
ex.code = code;
ex.errno = code;
ex.syscall = syscall;
Expand Down Expand Up @@ -377,9 +389,15 @@ function exceptionWithHostPort(err, syscall, address, port, additional) {
details += ` - Local (${additional})`;
}

// Reducing the limit improves the performance significantly. We do not loose
// the stack frames due to the `captureStackTrace()` function that is called
// later.
const tmpLimit = Error.stackTraceLimit;
Error.stackTraceLimit = 0;
// eslint-disable-next-line no-restricted-syntax
const ex = new Error(`${syscall} ${code}${details}`);
// TODO(joyeecheung): errno is supposed to err, like in uvException
Error.stackTraceLimit = tmpLimit;
ex.code = ex.errno = code;
ex.syscall = syscall;
ex.address = address;
Expand Down Expand Up @@ -410,9 +428,15 @@ function dnsException(code, syscall, hostname) {
}
}
const message = `${syscall} ${code}${hostname ? ` ${hostname}` : ''}`;
// Reducing the limit improves the performance significantly. We do not loose
// the stack frames due to the `captureStackTrace()` function that is called
// later.
const tmpLimit = Error.stackTraceLimit;
Error.stackTraceLimit = 0;
// eslint-disable-next-line no-restricted-syntax
const ex = new Error(message);
// TODO(joyeecheung): errno is supposed to be a number / err, like in
Error.stackTraceLimit = tmpLimit;
// uvException.
ex.errno = code;
ex.code = code;
Expand Down
16 changes: 10 additions & 6 deletions lib/internal/fs/utils.js
Original file line number Diff line number Diff line change
Expand Up @@ -190,23 +190,27 @@ function nullCheck(path, propName, throwError = true) {
const pathIsUint8Array = isUint8Array(path);

// We can only perform meaningful checks on strings and Uint8Arrays.
if (!pathIsString && !pathIsUint8Array) {
if (!pathIsString && !pathIsUint8Array ||
pathIsString && path.indexOf('\u0000') === -1 ||
pathIsUint8Array && path.indexOf(0) === -1) {
return;
}

if (pathIsString && path.indexOf('\u0000') === -1) {
return;
} else if (pathIsUint8Array && path.indexOf(0) === -1) {
return;
// Reducing the limit improves the performance significantly. We do not loose
// the stack frames due to the `captureStackTrace()` function that is called
// later.
const tmpLimit = Error.stackTraceLimit;
if (throwError) {
Error.stackTraceLimit = 0;
}

const err = new ERR_INVALID_ARG_VALUE(
propName,
path,
'must be a string or Uint8Array without null bytes'
);

if (throwError) {
Error.stackTraceLimit = tmpLimit;
Error.captureStackTrace(err, nullCheck);
throw err;
}
Expand Down
5 changes: 5 additions & 0 deletions lib/internal/process/warning.js
Original file line number Diff line number Diff line change
Expand Up @@ -127,8 +127,13 @@ function setupProcessWarnings() {
throw new ERR_INVALID_ARG_TYPE('code', 'string', code);
}
if (typeof warning === 'string') {
// Improve error creation performance by skipping the error frames.
// They are added in the `captureStackTrace()` function below.
const tmpStackLimit = Error.stackTraceLimit;
Error.stackTraceLimit = 0;
// eslint-disable-next-line no-restricted-syntax
warning = new Error(warning);
Error.stackTraceLimit = tmpStackLimit;
warning.name = String(type || 'Warning');
if (code !== undefined) warning.code = code;
if (detail !== undefined) warning.detail = detail;
Expand Down