Jest memory problems in node environment
See original GitHub issueš¬ Questions and Help
Stack:
System:
OS: macOS High Sierra 10.13.4
CPU: x64 Intel(R) Core(TM) i7-4770HQ CPU @ 2.20GHz
Binaries:
Node: 8.1.4 - ~/.nvm/versions/node/v8.1.4/bin/node
npm: 6.0.0 - ~/.nvm/versions/node/v8.1.4/bin/npm
npmPackages:
jest: ^23.0.1 => 23.0.1
sequelize: ^4.37.10
pg: ^7.4.3
koa: ^2.3.0
babel-jest: ^22.4.3
1. Preface
We recently switched all our APIās test from Mocha to Jest. We have around 90 tests, half of them require to run synchronously due to them using our testing database (running seeds between them), so we have to use --runInBand.
I unfortunately canāt share my code as it is private.
2. The problem
Running tests one by one was fine, I then tried to run all of them at once and things went bad. With the --logHeapUsage, it seems context memory isnāt GCād resulting in a Javascript heap out of memory.
I tried using the new option --detectOpenHandles to see what could prevent the GC to work but this is what came out:
ā PROMISE
at Promise.catch (<anonymous>)
at node_modules/core-js/library/modules/es6.promise.js:244:30
at Object.<anonymous>.module.exports (node_modules/core-js/library/modules/_iter-detect.js:19:5)
at Object.<anonymous> (node_modules/core-js/library/modules/es6.promise.js:243:74)
at Object.<anonymous> (node_modules/core-js/library/fn/promise.js:4:1)
I have around 6-8 of these, and no clue whatsoever of where to look.
I searched around and found out it was most likely the database connection so I added these as a global teardown:
afterAll(async () => {
await db.close(); // Sequelize instance
server.close(); // Koa server instance used with supertest (when needed)
});
This didnāt change much, memory still goes up very quickly (30-40 MB per test). In the end, I wrote a small file launching jest multiple times to avoid memory problems and stitching coverage report together but this isnāt ideal.
Issue Analytics
- State:
- Created 5 years ago
- Reactions:24
- Comments:52 (2 by maintainers)
Top GitHub Comments
@rickhanlonii, Iām also experiencing this bug, and I do suspect itās a bug with jestās module registry.
Jest does not cache modules as nodeās
require()
does and probably does not release properlyrequire()
'd modules between test suites.Created a repo to illustrate this issue.
@SimenB @cpojer @aaronabramov the source of the issue comes from fact that for every test run new modules cache is created but itās used for all but native node.js modules.
On example of
graceful-fs
, following happens when more than one test is run within same process (or worker) as handled by Jest:graceful-fs
is freshly required and decorates nativefs
functions directly onfs
object (note: through closuresgraceful-fs
is permanently attached tofs
and therefore cannot be freed)graceful-fs
is freshly required (again) within same process, and decorates (already decorated!)fs
functions directly onfs
object. Same way, itās permanently attached tofs
so not freed.ā¦ scenario repeats with every following test run.
So after a few test runs we deal with a situation, where after invoking single specific
fs
function, multiplegraceful-fs
functions are run recursively, and that I suppose is responsible for fastly growing memory leak.Similarly popular
Bluebird.promisifyAll(require('fs'))
attaches tofs
and introduces a leak. Still in that case itās not recursive so not that harmful.Iāve noticed this when trying to incorporate long stack trace solution (based on
async_hooks
) into Jest, and observed how quickly it was blown with OOM, cause was that initializations were made before each test run, when there should be one initialization per processWhen looking at Jest design, probably best solution would be to introduce an option in which we can list package names which should be handled by nodejs native modules loader (and
graceful-fs
could be there by default), so theyāre loaded once across all test runs within same process. As far as I see itās not possible now, and that makes addressing this difficult.Other solutions could be:
require
as provided by Node.js. It wonāt give desired isolation, but at least itāll reflect public Node.js API, which is not respected with current approach.