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

Memory leak when running Jest tests due to nock not restoring https global #216

Open
JordanAtBridgit opened this issue Jan 22, 2020 · 2 comments

Comments

@JordanAtBridgit
Copy link

JordanAtBridgit commented Jan 22, 2020

This is the same issue I mistakenly reported on the rekit-plugin-redux-saga repo (that ticket has been closed). I saw this comment on the rekit core repo, which led me to believe that this was the correct place to report the issue I'm seeing.

Description
Our CI build was running out of memory and crashing as the number of actions and Jest tests increased. Narrowed the issue down to our Redux action tests, and eventually found out that nock was the culprit. The same issue and the fix are described here: nock/nock#1448. I'm currently using rekit-core v2.3.4

Essentially, on import nock is modifying the global https object to intercept outgoing API calls, and that change needs to be restored with nock.restore(). The call to nock.cleanAll() just clears any mock calls that have been set up.

To Repro
In any project with multiple action tests, run the test suite with memory usage logging turned on like so:
node --expose-gc <your_test_runner.js> --env=jsdom --no-watch --runInBand --logHeapUsage

Expected
Memory usage should be more or less flat, with the heap size rising and falling as GC takes place

Actual
Specific experiences may vary depending on the tests being run, and total memory available, but in our case there is a linear increase in RAM usage growing by roughly 15-30MB per test until the test run completes, or a Node exception is thrown when memory runs out.

Note that this may be an issue that test runners other than Jest can compensate for or handle differently. We have an older Rekit project that was using Mocha, and this issue never came up.

Versions
Node: 10.5.0
rekit-core@2.3.4
rekit-plugin-redux-saga@1.0.0
rekit-studio@2.4.1

@mainfraame
Copy link

This is still an ongoing issue. The crux of the issue is the monkey patching of any native node modules. Other libraries like agent-base and graceful-fs have had these same issues in the past. From what I understand, they both provided patches that no longer cause Jest to report memory leaks. Jest also seems to have an issue whenever you're calling any of the console methods.

For anyone who is coming across this issue, I've found that a temporary workaround that will prevent the leaks is using this command:

node --expose-gc $(npm bin)/jest --silent --verbose --logHeapUsage

If you look in the jest source code; you'll notice that after closing a worker, jest will check to see if global.gc is exposed and call it. It doesn't work if you call it in your test, but it seems that after they tear down the environment object (vm context) and call global.gc(), the tests are successfully cleaned up.

@JordanAtBridgit
Copy link
Author

Thanks to @mentierd for reminding me that I opened this ticket and posting a workaround. For the benefit of anyone else looking into this same issue, what I ended up doing was using an alternate workaround found in the Nock issue linked in the description above.

Essentially, it seems that calling nock.restore() is required to revert native node modules so that the GC process can happen as expected. (I haven't looked at this since January, so the specifics are starting to get fuzzy for me.) Rather than add nock.restore() to all existing test files (and having to remember to do the same going forward) I made an afterEach.js file with the following contents to handle the cleanup in one central place:

import nock from 'nock';

global.afterEach(() => {
  nock.cleanAll();
  nock.restore();
});

In my case I also went back through existing test files and removed the redundant nock.cleanAll() calls, but that was more for neatness than because of any functional difference that I could see.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

2 participants