JSON.stringify on process.nodeModules can result in an OOM when using custom serializer #27536
Labels
help wanted
Issues that need assistance from volunteers or PRs that need help to proceed.
module
Issues and PRs related to the module subsystem.
I was working through an upgrade from Node 7.9 to the latest LTS at Node 10.15 when I was met with OOM errors that suddenly started happening post-upgrade. After some digging I discovered that the culprit was a call to JSON.stringify on an object that happened to contain a reference to NodeJS.Process (current process). The call to stringify process tries to enumerate
process.mainModule
NodeModule object and OOMs while trying to do so.Each NodeModule has two properties that point to the hierarchy of requires:
parent
andchildren
.parent
points to the current module's requirer andchildren
points to all modules this one requires. When running JSON.stringify on mainModule with default JSON.stringify settings it quickly falls out with a circular reference due to a child pointing to a parent. We are using a library called json-stringify-safe (https://github.com/moll/json-stringify-safe) that makes it so stringify does not throw on circular references. This library adds a circular marker instead of the would-be value into the JSON output without throwing, and runs until it exhausts every non-circular path. I have found that there was a change in behavior after Node 8 that caused this number of non-circular paths to explode exponentially in Node modules.From what I can tell in Node 7 each module is loaded and pointed to only once via the
NodeModule.children
property: only by the first parent to require the module. The child in turn points to that first parent viaNodeModule.parent
property. However, in Node 8+ every parent module holdschildren
references to every module this parent requires instead of just those it was first to require, while the child'sparent
property points to the first parent to have loaded this child. This means that every package to require some child module that it was not the first one to require will also end up traversing the entire tree of the first module to have loaded it when stringified.For a more illustrative example, imagine the following scenario:
This behavior multiplied out to dozens (hundreds?) of packages explodes exponentially easily chugging through gigs of RAM. The following script can be run to easily reproduce this issue on any version of Node after 8. You'll have to install the winston package for this example but there are others that will do it too: winston was just an easy one with lots of monthly downloads.
I ended up figuring out a simple fix for this in our code base by declaring the property pointing to
Process
as non-enumerable. This way for-in, stringify, or Object.keys cannot see it and won't try to delve in.I wanted to bring this up as a potential issue. The module hierarchy is obviously working as intended, but the hierarchy does have this potential of blowing out heap on attempts to traverse it. My gut reaction suggests making
parent
non-enumerable is easiest, but I also don't know what the intent or direction might be from Node maintainers or if this is even seen as a problem.The text was updated successfully, but these errors were encountered: