Skip to content
This repository has been archived by the owner on Oct 23, 2023. It is now read-only.

Parsestack refactor + tests to fix memory explosion #320

Merged
merged 4 commits into from
May 17, 2017

Conversation

LewisJEllis
Copy link
Contributor

@LewisJEllis LewisJEllis commented May 16, 2017

Fixes #319, based on the insight in #318; thanks to @Asuka999 for the original insight + PR and @cveilleux for the out of memory report.

  • refactors parseStackinto a more serial-procedural style with a readSourceFiles helper
  • adds a unit test to check that readFile is only called once per file
  • adds a manual memory test for capturing an error thrown from 1000-deep callstack in a 5MB file
    • without this fix, parsing that stack and reading it multiple times concurrently would run the node process out of memory

The parseStack code in general needed some overhauling, so I went ahead and did that in addition to just fixing the concurrency control flow. It should hopefully be more robust and easier to follow now.

/cc @MaxBittker @benvinegar

@LewisJEllis
Copy link
Contributor Author

LewisJEllis commented May 16, 2017

One notable point I forgot to mention before is that the read-source-files-cache is on a per-parseStack basis, so the cached files are not reused between multiple parseStack calls. This is the same as it was before and my statement in #318 that

we won't read a file again if a prior parseStack call already read it

was incorrect. I believe this is our ideal default behavior, so as to ensure that in a long-running process we won't accumulate a large cache which ends up consuming a significant amount of memory. It does make for higher disk read load in many typical cases, though; I think a future option for whether to keep the cache around between calls could be useful for users who are more concerned about disk read load than about memory usage.

frame.context_line = lines[frame.lineno - 1];
frame.post_context = lines.slice(frame.lineno, frame.lineno + LINES_OF_CONTEXT);
} catch (e) {
// anomaly, being defensive in case
Copy link
Contributor

@MaxBittker MaxBittker May 17, 2017

Choose a reason for hiding this comment

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

do you want to set the properties to be empty strings /arrays here?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

nah, i think we'd rather not send them to the server at all; not sure what behavior diff would be but not sending them at all is what we do for native frames so i'll stick to that

Copy link
Contributor

Choose a reason for hiding this comment

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

agreed

var uniqueFilesRead = filesRead.filter(function (filename, idx, arr) {
return arr.indexOf(filename) === idx;
});
filesRead.length.should.equal(uniqueFilesRead.length);
Copy link
Contributor

Choose a reason for hiding this comment

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

👍

stack.reverse();

return stack.forEach(function (line, index) {
var frames = [];
var filesToRead = {};
Copy link
Contributor

Choose a reason for hiding this comment

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

could new Object(null) here if you wanted idk

Copy link
Contributor Author

Choose a reason for hiding this comment

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

👍

Copy link
Contributor

@MaxBittker MaxBittker left a comment

Choose a reason for hiding this comment

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

this makes sense and seems to work locally on my dummy project. nice turnaround and solid improvement! lmk what you think about the toString

var numFilesToRead = filenames.length;
return filenames.forEach(function (filename) {
fs.readFile(filename, function (readErr, file) {
if (!readErr) sourceFiles[filename] = file.toString().split('\n');
Copy link
Contributor

Choose a reason for hiding this comment

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

should you wrap this line in a try catch, in case toString throws due to a weird file? to ensure that the callback always fires.

Copy link
Contributor Author

@LewisJEllis LewisJEllis May 17, 2017

Choose a reason for hiding this comment

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

if (!readErr) guarantees that file is a Buffer according to fs.readFile docs, and Buffers have toString (as linked) even if they're empty, so I'm not concerned

Copy link
Contributor

Choose a reason for hiding this comment

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

ok 👍

@LewisJEllis LewisJEllis merged commit bafded8 into master May 17, 2017
@LewisJEllis LewisJEllis deleted the parsestack-refactor-tests branch May 17, 2017 00:55
@LewisJEllis LewisJEllis changed the title Parsestack refactor + tests to fix memory leak Parsestack refactor + tests to fix memory explosion May 17, 2017
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

Out of memory in parseStack.
2 participants