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

fix: memory leak in large files with lots of errors #180

Merged
merged 2 commits into from
Mar 27, 2023
Merged
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
39 changes: 38 additions & 1 deletion lib/validators/schema.js
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,23 @@ const AjvDraft4 = require('ajv-draft-04');

const { getSpecificationName } = require('../util');

/**
* We've had issues with specs larger than 2MB+ with 1,000+ errors causing memory leaks so if we
* have a spec with more than `LARGE_SPEC_ERROR_CAP` errors and it's **stringified** length is
* larger than `LARGE_SPEC_LIMITS` then we will only return the first `LARGE_SPEC_ERROR_CAP` errors.
*
* Ideally we'd be looking at the byte size of the spec instead of looking at its stringified
* length value but the Blob API, which we'd use to get its size with `new Blob([str]).size;`, was
* only recently introduced in Node 15.
*
* w/r/t the 5,000,000 limit here: The spec we found causing these memory leaks had a size of
* 13,934,323 so 5mil seems like a decent cap to start with.
*
* @see {@link https://developer.mozilla.org/en-US/docs/Web/API/Blob}
*/
const LARGE_SPEC_ERROR_CAP = 20;
const LARGE_SPEC_SIZE_CAP = 5000000;

module.exports = validateSchema;

/**
Expand Down Expand Up @@ -48,13 +65,33 @@ function validateSchema(api, options) {
const isValid = ajv.validate(schema, api);
if (!isValid) {
const err = ajv.errors;

let additionalErrors = 0;
let reducedErrors = reduceAjvErrors(err);
if (reducedErrors.length >= LARGE_SPEC_ERROR_CAP) {
try {
if (JSON.stringify(api).length >= LARGE_SPEC_SIZE_CAP) {
additionalErrors = reducedErrors.length - 20;
reducedErrors = reducedErrors.slice(0, 20);
}
} catch (err) {
// If we failed to stringify the API definition to look at its size then we should process
// all of its errors as-is.
}
}

let message = `${getSpecificationName(api)} schema validation failed.\n`;
message += '\n';
message += betterAjvErrors(schema, api, reduceAjvErrors(err), {
message += betterAjvErrors(schema, api, reducedErrors, {
colorize: options.validate.colorizeErrors,
indent: 2,
});

if (additionalErrors) {
message += '\n\n';
message += `Plus an additional ${additionalErrors} errors. Please resolve the above and re-run validation to see more.`;
}

throw ono.syntax(err, { details: err }, message);
}
}
Expand Down
30 changes: 15 additions & 15 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -76,7 +76,7 @@
"@apidevtools/openapi-schemas": "^2.1.0",
"@apidevtools/swagger-methods": "^3.0.2",
"@jsdevtools/ono": "^7.1.3",
"@readme/better-ajv-errors": "^1.5.0",
"@readme/better-ajv-errors": "^1.6.0",
"@readme/json-schema-ref-parser": "^1.2.0",
"ajv": "^8.12.0",
"ajv-draft-04": "^1.0.0",
Expand Down

Large diffs are not rendered by default.

Loading