jest: [Bug]: Memory consumption issues on Node JS 16.11.0+
šØ As of Node 21.1.0, this has been fixed upstream. Hopefully the fixes will be backported to v18 and v20 as well (as of writing (Oct. 26 2023) they have not), but that is up to the Node.js project and nothing we control from here. Note that (native) ESM still has memory leaks - that can be tracked here: #14605. If youāre unable to upgrade your version of Node, you can use --workerIdleMemoryLimit
in Jest 29 and later. See https://jestjs.io/docs/configuration/#workeridlememorylimit-numberstring šØ
Version
27.0.6
Steps to reproduce
- Install the latest Node JS (16.11.0 or later) or use the appropriate Docker image
- Set up a project with a multiplicity Jest tests
- Run
node --expose-gc node_modules/.bin/jest --logHeapUsage
and see how the memory consumption starts increasing.
Expected behavior
Since Jest calls global.gc()
when Garbage Collector is exposed and --logHeapUsage
flag is present, the memory usage should be stable.
Actual behavior
The memory usage increases with every new test
Additional context
We had some issues with Jest workers consuming all available RAM both on CI machine and locally.
After doing some research, we found that if we run Jest like the following node --expose-gc node_modules/.bin/jest --logHeapUsage
, the heap size remains stable. After upgrading to Node JS v16.11.0, the issue was back. Node v16.10.0 works fine. I believe it was something accidentally introduced in the new Node, but it might be useful to take a look at this from Jest perspective in search of possible workarounds.
Iām also having the same behavior on my working machine, environment of which Iām pasting below šš»
Environment
System:
OS: macOS 11.6
CPU: (8) x64 Intel(R) Core(TM) i7-7700K CPU @ 4.20GHz
Binaries:
Node: 16.11.0 - ~/.nvm/versions/node/v16.11.0/bin/node
Yarn: 1.22.0 - ~/SomeFancyDir/webapp/node_modules/.bin/yarn
npm: 8.0.0 - ~/.nvm/versions/node/v16.11.0/bin/npm
npmPackages:
jest: 27.0.6 => 27.0.6
About this issue
- Original URL
- State: closed
- Created 3 years ago
- Reactions: 233
- Comments: 285 (93 by maintainers)
Commits related to this issue
- chore: replace `vm.Script` with `vm.compileFunction` Memory leak is alleviated with `vm.compileFunction`. However, implementing `importModuleDynamically` causes memory to leak again, so that has been... — committed to rthreei/jest by rthreei 3 years ago
- chore: replace `vm.Script` with `vm.compileFunction` Memory leak is alleviated with `vm.compileFunction`. However, implementing `importModuleDynamically` causes memory to leak again, so that has been... — committed to rthreei/jest by rthreei 3 years ago
- chore: replace `vm.Script` with `vm.compileFunction` Memory leak is alleviated with `vm.compileFunction`. However, implementing `importModuleDynamically` causes memory to leak again, so that has been... — committed to rthreei/jest by rthreei 3 years ago
- chore: replace `vm.Script` with `vm.compileFunction` Memory leak is alleviated with `vm.compileFunction`. However, implementing `importModuleDynamically` causes memory to leak again, so that has been... — committed to rthreei/jest by rthreei 3 years ago
- chore: replace `vm.Script` with `vm.compileFunction` Memory leak is alleviated with `vm.compileFunction`. However, implementing `importModuleDynamically` causes memory to leak again, so that has been... — committed to rthreei/jest by rthreei 3 years ago
- chore: replace `vm.Script` with `vm.compileFunction` Memory leak is alleviated with `vm.compileFunction`. However, implementing `importModuleDynamically` causes memory to leak again, so that has been... — committed to rthreei/jest by rthreei 3 years ago
- chore: replace `vm.Script` with `vm.compileFunction` Memory leak is alleviated with `vm.compileFunction`. However, implementing `importModuleDynamically` causes memory to leak again, so that has been... — committed to rthreei/jest by rthreei 3 years ago
- chore(package.json): run jest with --no-compilation-cache --expose-gc Refs: https://github.com/facebook/jest/issues/11956#issuecomment-1001976394 — committed to trivikr/aws-sdk-js-v3 by trivikr 2 years ago
- chore(deps-dev): update to node 16 (#5343) #### Details This PR bumps our default/recommended build environment to Node 16. There aren't any real breaking changes affecting us here, except that... — committed to microsoft/accessibility-insights-web by dbjorge 2 years ago
- Deactivating Unit Tests due to jest memory handling issue This temporarily deactivates unit testing for all commit methods due to a memory handling issue in jest, combined with node versions > 16.10.... — committed to BastianMuc/Superalgos by BastianMuc 2 years ago
- ci: check node 16.10 still has the memory leak issue The one mentioned here: https://github.com/facebook/jest/issues/11956#issuecomment-1234469566 Might be completely irrelevant. — committed to PostHog/posthog by deleted user 2 years ago
- Downgrading node to 16.10 due to memory issues with jest and node 16.10+ (https://github.com/facebook/jest/issues/11956) — committed to eijawerner/neo4j-browser by eijawerner 2 years ago
- Speeding up jest tests This speeds up the running of jest tests by enabling isolatedModules which has the effect of [disabling typechecking](https://kulshekhar.github.io/ts-jest/docs/getting-started/... — committed to ministryofjustice/hmpps-template-typescript by andrewrlee 2 years ago
- Speeding up jest tests (#116) This speeds up the running of jest tests by enabling isolatedModules which has the effect of [disabling typechecking](https://kulshekhar.github.io/ts-jest/docs/getting-... — committed to ministryofjustice/hmpps-template-typescript by andrewrlee 2 years ago
- Speeding up jest tests (hmpps-template-typescript#116) This speeds up the running of jest tests by enabling isolatedModules which has the effect of [disabling typechecking](https://kulshekhar.github.... — committed to ministryofjustice/hmpps-incentives-ui by andrewrlee 2 years ago
- Sync `hmpps-template-typescript` and update dependencies (#302) * Speeding up jest tests (hmpps-template-typescript#116) This speeds up the running of jest tests by enabling isolatedModules which... — committed to ministryofjustice/hmpps-incentives-ui by ushkarev 2 years ago
- feat(@angular/cli): add support for Node.js version 18 Pacote version 14 does requires `14.17.x` or `16.13.x`. BREAKING CHANGE: The Angular CLI no longer supports `16.10.x`, `16.11.x` and `16.12.x`... — committed to angular/angular-cli by alan-agius4 2 years ago
- Try downgrading Node Per https://github.com/facebook/jest/issues/11956 — committed to iron-fish/ironfish by dguenther 2 years ago
- Try fixing memory usage of CI tests (#2401) Downgrade Node per https://github.com/facebook/jest/issues/11956 — committed to iron-fish/ironfish by dguenther 2 years ago
- chore: set node/jest memory optimisation params See: https://github.com/facebook/jest/issues/11956 — committed to europeana/portal.js by rwd 2 years ago
Weāve been recently hit by this bug (or combination of them) in several services of our stack. Itās been an inconvenience for use since once the heap size exceeded the 2GB available by GitHub Actions machines, our CI/CD process was failing 100% of the time.
I believe the āmodules accumulating in the heapā bug has been present in Jest for some time, but that in combination with the changes in Node from
v16.10
tov16.11
has made it much more severe. Weāre certain that bug exists in Jest, as weāve managed to replicate it with a ādummy repoā, as several other people have stated.The fact that the ticket created in Node JS was closed as a WONTFIX is a bit worrying. I currently lack the knowledge to judge the discussions which happened there, but if that wasnāt a bug at the end probably the solution falls on the Jest side.
After trying to extract as much information from the different GitHub issues that have been created in Jest, TS-Jest and Node, and trying several approaches, the only path for us was to downgrade back to Node
v16.10
.These are some of the statistics we gathered in the process:
@jest/core
Some explanations about the columns:
@jest/core
: version of the Jest corejest.config.json
--runInBand
Jest flag--no-compilation-cache
flagThe most relevant tickets related to this matter:
We hope this issue is given the importance it deserves, as keeping the Node version pinned to
v16.10
or relying on bigger CI machines to compensate this is a poor strategy for the future.Node 20 with backport coming Thursday: https://github.com/nodejs/node/pull/50682
Could a maintainer update the top post with a summary of the current state of whatās known perhaps?
Hereās my attempt at a quick summary, a better one would probably also have links to relevant comments from up-thread.
workerIdleMemoryLimit
workaround was added to Jest 29 which allows capping the maximum memory in use by a worker, at which point itāll be killed and replaced with a fresh workerrunInBand
without one of the patches listed aboveworkerIdleMemoryLimit
only really needs to be set high enough to run a few test suites - lower seems to be better as that causes the workers to be recycled more often rather than building up loads of memory usageThe commits that fixed it are in the
vm: fix V8 compilation cache support for vm.Script
part in that blog post, where Jest was also mentioned, though it may not be immediately obvious why. I think the issues described from the Jest side are multi-faceted and as https://github.com/jestjs/jest/issues/11956#issuecomment-1757678185 mentioned this only mitigated the regressions for pure CJS compilation (i.e. regression comes back when thereās actualimport()
or--experimental-vm-modules
).To summarize, the regression was introduced when Node.js implemented the
importModuleDynamically
option back in v16, to retrieve this option whenimport()
is called within the script compiled, Node.js needs to set a host-defined option for this script. Previously, when V8 re-compiled a script that it has seen before, it could do a quick lookup on an internal table and return the script thatās already compiled, making subsequent compilation almost free. The introduction of host-defined options resulted in cache misses, because in the implementation of Node.js up until not long ago, this option was different for every script compiled. The cache miss not only resulted in a performance regression (then V8 had to compile the previously-seen scripts from scratch every time), but could also contribute to higher memory usage (every script subsequently compiled is cached again with a slightly different option even though the source code remains the same). There was also another source of memory leak coming from how the scripts are managed within Node.js. To fix the issues, we worked around the problem in Node.js by:importModuleDynamically
isnāt configured https://github.com/nodejs/node/pull/49950 - and because itās a constant for all scripts compiled without this option, the cache can be hit again in this case. However, this would still require some changes from Jestās side, so we did 3.importModuleDynamically
is always configured but will throw a warning when--experimental-vm-modules
isnāt set (because then you canāt actually do anything useful with that option anyway), we use another constant option to throw a built-in warning that serve the same purpose in https://github.com/nodejs/node/pull/50137, which finally leads to automatic fix of this issue in v21.1.0 without further changes from Jestās side.So the issues are only mitigated, but not yet fully fixed - it comes back when
--experimental-vm-modules
is used. See https://github.com/nodejs/node/issues/35375#issuecomment-1773705384 about remaining issues & ideas about how they can be fixed/worked around, so itās still work in progress.Our company created a custom runtime based on the patch above that is located here: https://github.com/reside-eng/jest-runtime
This runtime will only work with Jest v28 and it will not work if you are using ESM modules. There is some additional information in the README.md about using swc if you are also using Typescript, but this is not required to use the custom runtime.
then add the following to your jest.config.js
For those who are deeply embedded in using Jest and donāt have the bandwidth to move to another suite (e.g. vitest), the NodeJS team has a potential fix in a draft PR per this comment.
From what i understand the underlying bug fix in v8 just landed yesterday https://chromium-review.googlesource.com/c/v8/v8/+/4834471
Now porting of it in node is pending https://github.com/nodejs/node/pull/48510
With those results (thanks for verifying!), I think we can close this. Iāll update the OP as well.
š
Node 20.10.0 has been released including the fixes mentioned in this PR š
https://github.com/nodejs/node/releases/tag/v20.10.0
21.1.0 is out with all the fixes: https://nodejs.org/en/blog/release/v21.1.0
Please give it a try š
I did some testing of the various potential fixes, and here are my results, comparing heap usage vs. execution time.
Note: I forced
jest --maxWorkers=2
for all tests, since the jest option--workerIdleMemoryLimit
needs workers > 1Note: regarding Heap Usage: For the tests with
workerIdleMemoryLimit
set, I was unable to determine max heap usage accurately, so I just noted what jest reported before a respawn. But it might be more accurate to peg it at 1700x2 = 3400MB as āmaxā usage.Test Machine Specs
Testing was done on my local laptop. There are 350+ test suites with a total of 3000+ tests. The tests are written in typescript (as is the source), and I utilize
ts-jest
. This may have had an effect on my results vs. nonts-jest
scenarios.Parameters Involved
There were four prarameters involved:
--no-compilation-cache --expose-gc
vs [none] (denoted by-ncc -egc
in the graphs)@side/jest-runtime
(thanks @a88zach ! Check it out https://github.com/reside-eng/jest-runtime)Total 4 parameters, 2^4 = 16 data points.
Comparing memory usage in v16.10 vs v18.12
The āleakā in >= v16.11 is quite apparent when no node options / special jest config is used. The v18 test uses almost 3x as much! However, using the jest param
workerIdleMemoryLimit
(Jest v29+) can cap this quite well.If you want to purely optimize for memory usage, this param alone should be enough to make tests runnable in your environment. Interestingly,
Comparing execution time in v16.10 vs v18.12
For v18.12, execution time seems to be reduced by either using
@side/jest-runtime
as well asworkerIdleMemoryLimit
. using both reducded it even more.The winner for execution time is v16.10 with
workerIdleMemoryLimit
, butv18.12
can come pretty close.The
--no-compilation-cache
and--expose-gc
node options increase execution time massively for both v16.10 and v18.12.Comparing memory usage vs execution time
Finally, we use a bubble chart to try and find the ābest compromiseā. The same color indicates same node options / jest config, with one being v16.10 and the other v18.12
Iāll allow you guys to draw your own conclusions from this.
Conclusion
If you previously did ānormalā jest testing without any node options / tweaking, then memory usage in v16.11+ was a massive problem. But it seems the
workerIdleMemoryLimit
param does indeed help in this case, and execution time can be similar-ish.Iād advise you guys to try it out, and tweak it every 100MB to find what works best (results do vary quite a bit, and will depend on your test suite).
Raw Results
What worked for me was combining the 2 node flags:
--no-compilation-cache
and--expose-gc
. Using this example above Iāve managed to runnode --no-compilation-cache --expose-gc ./node_modules/jest/bin/jest.js --runInBand --logHeapUsage
and got heap sizes between 22MB and 25MB. Also using these flags the max heap size in my codebase decreased from ~2000 MB to ~400MB . Tested onnode v16.11.0
. Hope it helps !If anyone is looking to use the new workerIdleMemoryLimit setting and still run test serial, I was able to hack the jest-runner as seen below:
Now, whenever the heap grows over 1GB, it will kill the worker and spawn a new one
Same here. On node 16.13.1, heap size goes up to 500MB while on node 16.10, it stays at 50MB with the following repro:
Edit: install Volta and do
volta run --node 20.1.0 -- node ./node_modules/jest/bin/jest.js --runInBand --logHeapUsag
to test a specific node version@alexw10 I would strongly recommend everyone still on jest to migrate to vitest these days. It is faster, more stable, API is for the most part a drop-in replacement. And facebook gave up on jest.
I can confirm that problem persists on Node 18.15.0
Following a suggestion on the Node issue, I tried running with the Node option
--no-compilation-cache
. Suddenly my CI with 5000 tests works again, and furthermore it finishes without the ~1GB of memory leakage that I always saw under Node 14 (and of course without the 5GB+ that would leak under Node 16.13 until OOM). The downside is that it seems to take about 25ā50% longer to finish.For those visiting from search engines, I have some bad news. This is not fixed as of 1/6/2023. There are two potential fixes:
workerIdleMemoryLimit
configuration option--no-compilation-cache
flag (your tests will finish, but will run far slower)The underlying leak seems to have been introduced in V8 (bug report). Jest introduced
workerIdleMemoryLimit
to help alleviate this problem until a better solution can be found. It utilizes the brute-force approach of tearing down test workers whose idle memory is over a certain threshold (e.g., if they have memory that isnāt being freed).Replacing Jest isnāt really an option for us, so in order to Upgrade from Node 14 we had to upgrade Jest, as well. Quite a fun week.
This one on the other hand might solve it š
https://github.com/nodejs/node/pull/49950
Can confirm: 21.1.0 means we can finally run our massive
jest -i
database test suite again (previously we had to split it into 4 separate runs to avoid running out of RAM). Total execution time is down from2m12
for the four split test suites to1m33
for running the entire lot. Huge thanks to everyone involved, and weād love to see this backported to Node 20 (and ideally Node 18) so we donāt have to split the runs in CI any more. Thanks again ā¤ļø@kibertoad This issue stems from the Node and the
vm
module, not some unfixed issue in Jest. Itās true that (afaik) Facebook is no longer contributing to Jest development, but there is still an active community and a maintainer who very quickly made a workaround for the issue in this thread ā lets not belittle their efforts.This last week Iāve been doing a whole bunch of testing against our repo to see if I can figure out the cause of this issue. Ultimately Iāve not had any luck. Iāve tested Node directly with
vm.Script
and I cannot get it to leak. Iāve also tried jest directly with 1000ās of tests and I cannot get that to leak either in a small isolated test case. I can only get the leaking to reliably occur when used with our application, however I donāt believe itās our application actually at fault as others are experiencing the same issue.There are 2 main issues Iāve uncovered:
workerIdleMemoryLimit
config option, if set after every test file execution the memory usage of the worker is checked. If it exceeds the specified limit the worker is restarted.Here is the memory usage of 1 worker.
Experiencing the same with latest node LTS (18.16.0) and jest 29.5.0
Iāve run some tests considering various configurations. Hope it helps someone.
@PAkerstrand some notes:
vm.Script
and so running Node 16.11+ in production should not experience the performance regression.vm.Script
incorrectly, then the fix is unlikely to be in Jest space.What to do? Some options:
vm.Script
.So it looks like the patch can at least unblock Node upgrades, but the tradeoff is that the test suite is significantly slower to run.
--max-old-space-size=700
--max-old-space-size=2048
--no-compilation-cache
Interestingly, in the fastest option (
--no-compilation-cache
), I also see the unbounded growth of the heap go away. So it does seem like https://github.com/nodejs/node/issues/40014 and the corresponding (closed/wontfix) V8 bug seem particularly suspect: https://bugs.chromium.org/p/v8/issues/detail?id=12198I tested 19.8.0 with the memory leak fix in and it did not improve.
Iāve been running performance tests on jest tests and most of the time taken with jest is in the fact that at the start of every spec, files are re-imported again. So this means anything in your environment (jsdom), any serializers, shims, jest setup files and everything that your spec file imports.
So for instance I doubled the speed of our tests by:
This bug specifically affects large number of files being repeatedly imported every spec - so the worse your setup is w.r.t. the above, the worse the bug affects you, where as if each test is very isolated, importing a minimal number of files, then probably the underlying v8 bug makes no difference.
Hope this helps!
@EternallLight would you be willing to edit your original issue and put at the top a short workaround explanation? Thereās an awful lot of people dealing with this problem unaware workarounds already exist. Something along the lines of:
Workaround
Using
--workerIdleMemoryLimit
is successfully able to bring heap usage under control however you must use at least 2 workers and not run in band.Weāre also experiencing this issue. Node 16.11+ and Jest v27 consumes significantly more memory. Node 16.10 and Jest v27 seems OK.
Iāve been monitoring memory consumption for quite a while where I work and this is GREAT š: ~300MB drop comparing 10 runs average
6m8s ā> 4m21s run time ššš
ESM is separate from this issue.
Node 16 is EOL, but this might get backported to v18 and v20
By following upstream Node. Iām sure somebody will post here if (hopefully when) a backporting PR is opened.
Yeah, PR sent for the above (https://github.com/nodejs/node/pull/50137), meaning no changes to Jest will be necessary. If and when that lands, we can close this issue in Jest š
Note that if native ESM (including dynamic
import
s) is used, there will still be code cache issues. But the ānormalā case (CJS) should be fixed allowing people stuck on older versions to upgrade šI wonāt be backporting, but it sounds like Node might be able to handle it: https://github.com/nodejs/node/pull/49950#issuecomment-1741032282
If they end up not doing so, patching via yarn patches (or
patch-package
) should prove trivialWanted to give everyone in this information on what we learned working through this issue in our environment.
We utilize azure devops we have agents on this that we are using that have 32GB memory and we moved to 64GB memory machines. This essentially solved the issue for us there are multiple pipelines that can be run simultaneously and all run. We currently have 4000 tests that are now running pretty fast. 3 simultaneous builds on average the unit testing portion now runs in ~5 minutes.
We have the
maxWorkers
set to4
We have theworkerIdleMemoryLimit
set to2.5G
With these settings we have seen the best results speed wise as well as being able to run up to 20 simultaneous jobs at once.
Just wanted to give out some insight as to what has worked for us. Really hope Jest/Node is able to solve this problem some day so these hard coded values are no longer needed and appropriate things are just automatically figured out as well as no memory leaks are happening.
Hey there,
weāve got a react-native project (heavily relying on mocks) that has gotten unbearably slow and started failing because of running out of memory. While Iām sure our tests, mocks, snapshots, etc. have a lot to be improved on, the following tables comparing the same project across node version, and the results are āinterestingā.
using
jest@26.6.2
,ts-jest@27.1.5
using
jest@29.0.0-alpha.6
,ts-jest@29.0.3
Node 18 with pointer compression did not make a positive difference. Adding
--workerIdleMemoryLimit='800MB'
didnāt make a difference at all.What did make a massive difference was running node with
--no-compilation-cache
, no leaks, similar memory consumption for all node versions, basically confirming https://github.com/facebook/jest/issues/11956#issuecomment-991796821 and https://github.com/facebook/jest/issues/11956#issuecomment-994914988 a year later still is the case.using
jest@29.0.0-alpha.6
,ts-jest@29.0.3
https://github.com/facebook/jest/releases/tag/v29.0.0
Hereās the result of my analysis on my repo (all numbers are in seconds). Note that this subset runs ~1300 tests across ~130 files. Each files imports ~700 other files (our test harness is heavy) From this, it seems that
compileFunction
does bring performance of 16.11 similar to 16.10 BUT 16.10 with new Script was the fastest configuration so it still results in a heavy performance reduction. At this point I almost wonder if jest should add a configuration to let people choose which version (new Script or compileFunction) to use.Right now we decided to pin to node 16.10 in our test suite and will keep using the
new Script
approach and I just hope it gets resolved at node.js/v8 level before we have to updateAfter some digging, the suspicion surrounding
vm.Script
seems to be reinforced. This might help: https://github.com/facebook/jest/pull/12205We are close to victory folks š , 1 approval at a time!
https://github.com/docker-library/official-images/pull/15761
Rerunning my minimal repro with node 20.1.0 still gives ~300MB heap size vs ~80MB in node 16.10.
Node 18 ( node 16 on CI with same behavior ). Jest 29. Was getting like 1.5+gb mem usage per worker. After switching to coverage: āv8ā it start to OOM whether it runs with workers or runInBand. The only fix i found which decreased mem usage both with or without v8 coverage signifinactly from initial 1.5gb to around 500-800mb was 3 flags
--no-compilation-cache --expose-gc --logHeapUsage
Removing either one of it - bring back OOM. Doesnt make much sense, for example why logHeapUsage would decrease memory usage and prevent OOM and also actually increase speed? Profiling/logging should make it worse, not betterā¦ Something fishy happens here.Ours was due to superfluous transpilations
node_modules
, so jest was transpiling all the modules in there, including, for example, MUI 5.Takeaway is, ensure these issues are not caused by transpilation resources.
How did we derived at that?
eval
option for dev builds).<rootDir>/../node_modules/
totransformIgnorePatterns
, we got the tests behaving again.Future work
babel-jest
.I expanded the tests done here: https://github.com/facebook/jest/issues/11956#issuecomment-967274859
I tried also --no-all-sparkplug, verified the flag was passed to node correctly and node supported it. I tried switching sparkplug on in 16.10.0 as well - and I agree with the findings, no sparkplug correlation.
I then reproducing on v17 nightlies:
Which means this commit is bad:
https://github.com/nodejs/node/commits/v17.x?after=36ad9e8444a0f0a481a70a9f386e35f4037e3cec+1238&branch=v17.x
And it is pretty much all to do with upgrading v8 to v9.4
For those interested, Iām inviting you to join us in a live discussion about this problem. Weāll be online over Zoom for two hours on Friday at 8am ET to discuss how we might troubleshoot/fix this regression. Details below.
Calendar invite
https://calendar.google.com/event?action=TEMPLATE&tmeid=MWw1YWpoZGRpZXBsaGF2aWs0MGhndnA3MzkgcnNoYXJtYUBicml6YS5jb20&tmsrc=rsharma%40briza.com
Zoom link
Topic: Node.js 16.11.0+ / Jest - Memory consumption Time: Dec 17, 2021 08:00 AM Eastern Time (US and Canada)
Join Zoom Meeting https://briza.zoom.us/j/86836754356?pwd=SVI2dUxIVUlScGlRdEpFYXJtU3NCZz09
Meeting ID: 868 3675 4356 Passcode: 580759 One tap mobile +13126266799,86836754356#,*580759# US (Chicago) +13462487799,86836754356#,*580759# US (Houston)
Dial by your location +1 312 626 6799 US (Chicago) +1 346 248 7799 US (Houston) +1 646 558 8656 US (New York) +1 669 900 6833 US (San Jose) +1 253 215 8782 US (Tacoma) +1 301 715 8592 US (Washington DC) Meeting ID: 868 3675 4356 Passcode: 580759 Find your local number: https://briza.zoom.us/u/kdRQiLHyl
@glenjamin
This has been fixed with in the latest version? https://github.com/facebook/jest/releases/tag/v29.4.3
Iāve āsolvedā this issue with the following workaround (works on any version of node and jest as long as more than 1 worker is used)
TestEnvNode.js
jest.config.js
by killing the worker in a ācontrolled wayā this trick avoid fatal crash of the whole jest suite and Heap memory of each worker never goes over 1gb. in my use case (840 test suites, ~6k tests) this solution is also 30% faster than running in node 16.10.
unfortunately this solution does not work if runInBand is enabled or when running on a single worker (
-w1
) as jest will not spawn workers.Iāve got the Alpha 5 release installed locally on our problematic project and with
workerIdleMemoryLimit: '200MB'
it is working perfectly. I think that memory limit will need tuning based on your own circumstances so that it doesnāt recycle too frequently but the point is that itās working and not crashing out due to filling the heap.I would appreciate if others could do some validation and make sure itās working for them before this gets released as stable.
I was trying to isolate the issue from our codebase and created this repo. It looks like that even for very simple test suites, the problem is there.
For those wanting to see the actual upstream issue fixed from what I can tell
https://github.com/nodejs/node/issues/44211 https://github.com/nodejs/node/issues/42080
cover the issue in node.
and afict a proper fix is waiting on this change in v8 https://chromium-review.googlesource.com/c/v8/v8/+/3172764
It would be interesting to work out if the jest performance issue is solved in 19.8.0 when a attempted fix landed, before it was then reverted.
Yep, same with v18.15.0
Hello !
I had a huge memory consumption issue on my project after updating to node 16.17.1 and jest 26.6.3. Finally I gave a try at jumping to 29.0.0-alpha.5 and it resolved my issue !!
Thank you @phawxby and @SimenB for the workerIdleMemoryLimit parameter ā¤ļø
@nover I think youāre approaching this from the wrong side.
runInBand
is supposed to run tests serially in the current process. Thereās advantages to doing that, faster test results and normally lower memory footprint, plus it makes it much easier to debug what is going on. I think the issue youāre having stems from the fact thereās currently no way to run tests serially with a worker, and that is partially down to the fact internallyworkers: 1
is viewed asrunInBand
.My suggestion is that the internal workings of that should change and
maxWorkers
should refer to actual number of workers spawned. 0 = run in band, no workers. >= 1 spawn workers. But even then that wouldnāt fully solve the problem because jest will try to run in band when possible based on previous test performance for example.Both of the PRās iāve opened that could address your issue are definitely patch fixes and it we were going to do something more long term it would need some proper design and thought.
@nover unfortunately thereās nothing in the coming release to help you, without workers thereās nothing for it to restart. A possible solution might be to change the current
shouldRunInBand
rule so that it ignores the current number of workers. That way it makes it possible to run tests linearly (-w=1
) but without requiring them to run in the same process so that it spawns a worker. If you wanted to do that it would be here but would probably need feedback from @SimenB. If we were going to do it now would be the time though with a major release coming.@stephenh
--expose-gc
is independent of--logHeapUsage
, you can do one without the other. The docs suggest itās possible to use in combination to better diagnose problems. The function used isprocess.memoryUsage()
. Now Iām reading the docs you could be right, it might be helpful to point out itās not required.I had this issue with our test suite after our upgrade from node v14 to v16.14+. We had coverageProvider set to v8. After removal (using babel as described as default in the docs), memory issue was fixed.
@pwadmore-ea this was asked over here in the Node.js repo:
Seems like itās being backported here:
FWIW, that PR will currently need this diff in Jest to work
Hopefully that can be dealt with in Node itself so the performance improvement is not reliant on a change in Jest (which would necessitate people upgrading their Jest version)
Like mentioned in https://github.com/jestjs/jest/issues/11956#issuecomment-1719764041, those fixes only help with segfaults when using ESM and
import()
, and not the runaway memory consumption caused by missing code cache. So while very welcome fixes, it doesnāt help with the bug in this issue, unfortunately.Iād be super happy to be proven wrong here, of course š
See https://github.com/nodejs/node/issues/35375#issuecomment-1003411096
@sunilsurana āJust use vitestā is one of the best answers in 2023. Itās API-compatible with Jest, so migration is trivial.
You should only run with
detectOpenHandles
when youāre trying to troubleshoot an issue with your tests as itās a diagnostic flag, otherwise it will make all tests run serially (same asrunInBand
) and will introduce severe slowdown/memory increase as you mentioned. See docs.@sunilsurana
This is a node issue so nothing that Jest can do. The contributors for node did try a fix but had to revert the change due to an issue.
Have you tried running your jest with the following flags:
node --expose-gc --no-compilation-cache ./node_modules/jest/bin/jest.js <any other jest flags that you use here>
We found this reduced our maximum heap size from 3.6GB to 0.8GB
@blimmer Using
v8
as the coverage provider from the defaultbabel
is not a 1:1 switch (more information per this issue). As it may resolve the OOM issue for some or most, it may also throw off coverage thresholds in CI pipelines.We have also recently hit this with v18.15.0 @didaquis so can confirm it still persists it seemsā¦
What helped to resolve this issue temporarily in my case:
jest
as follows:NOTE: I am using
2048
above because only this amount of memory lets some of my tests pass without getting OOM. Found this value experimentally. You might need to tweak this value for your particular use case.NOTE: I am using the following versions:
My suspicion is that constricted CI environments may not handle Jest parallel mode well even if limited to a single worker. In my case, jobs were getting stuck and had to be manually stopped.
It may be not directly related to this issue, but, aside from the other options discussed here, I am currently also sharding executions to reduce some of the load and accelerate test execution. With fewer tests to run per CI container, there is less memory build-up in each one.
I doubt they ever will - nobody chimed in when bot marked them as stale, so both are closed now.
Iām facing a similar issue of significantly increased memory during tests with
node@18.13
it went up from ~800MB per 30+ test files, to 4000MBWeāre on a NX monorepo and using
yarn@3.3.1
andjest@29
an important difference between
node@16
andnode@18
is that there are way more modules imported => requirerequireModuleOrMock
calls for already existing/loaded modulesThat was visible in the chrome
Allocation sampling
memory profiling toolā¦If the jest maintainer need so - I can prepare a reproduction repository but that would eat up a lot of my time for today and Iām not itās worth it at allā¦
this is also useful
@phawxby updated it
Unfortunately
--no-compilation-cache
does not do the trick for us, still getting memory issues withjest@29.2.2
and node 18.10Itās not a solution for jest itself, but Iāve written alternative runner, which uses posix fork(), and it does not have memory leaks by design. Itās very POC now, have some limitations. https://github.com/goloveychuk/fastest-jest-runner
Personally I use it like this :
jest --config jest.base.config.mjs --logHeapUsage --workerIdleMemoryLimit='800MB'
But first you have to set jest@29.0.0-alpha.5
Iām curious about the design decision in
jest
: why isrun in band
not just a single worker by default? Is there a decision record somewhere you can point me in the direction of, so I can level up onjest
internal knowledge?I understand the sentiment regarding gnarly code, but see it from āour sideā. Weāre using
jest
notv8
, and weāre having a big problem š Our test suites donāt work (along with a lot of otherās). Also that particular v8 issue seems to be closed aswont fix
, so one could be inclined to say that itās now a feature of v8ā¦ š¤·Please consider your
jest
users. Weāve spent (wasted?) over 40 hours in the team now trying to circumvent a failing suite rather than creating features for the business. That PR solves it.We had consistent out of memory errors on node 16.13, reverting to 16.10 solved the problem for us.
I donāt think jest is the place where that gets decided
Iāve verified the new version of Node doesnāt help with the bug in this issue. See my post here: https://github.com/nodejs/node/pull/49932#issuecomment-1740542932
Hey @M-a-c-Carter, did you try the latest nightly node v21? It seems, the change that might improve things is not yet backported to v20, and probably wonāt be.
I think thatās still https://bugs.chromium.org/p/v8/issues/detail?id=10284. It just received a comment saying things might help now. However, see https://github.com/nodejs/node/pull/48510#issuecomment-1717670338
Code caching is the main problem reported in this issue. Of course with more than 100 participants here, that has probably been conflated with other memory issues, but that regression between 16.10 and 16.11 is as of yet not fixed upstream.
@kibertoad for small projects, yes. Iām trying to migrate a large project to vitest and itās a total pain (and vitest is not fast as it claims - many people have reported it being slower than Jest/SWC and I also witnessed a 40%+ time increase in test execution). But still worth a try.
Since I spent far too long on this, I thought Iād post for anyone else still struggling with this.
If youāre using a monorepository like nrwl/nx and find that āworkerIdleMemoryLimitā still doesnāt work for you:
Try a dumb value like
"workerIdleMemoryLimit": "ROFL"
, if it doesnāt completely fail/exit, the config value isnāt being used. I was trying to use it in the root jest.config.ts which isnāt used at all and needed to put it inside jest.preset.js to have each library config inherit it. Or you can place it into each individual library/app jest.config.ts.After setting it to ā1Gā I could see the memory dropping after exceeding 1G by running the following and testing it:
No, nor in node 20.
Relevant node issues
Posting this incase anyone else finds this useful, my company has a pretty large frontend monorepo where we shard our Jest tests across a number of CI boxes and each CI box has
16
CPU cores.We found that after upgrading from Node.JS
v16.10.0
tov16.20.0
that our Jest tests started timing out in CI. We were able to resolve the issue by changing the number of Jest workers spun up from--maxWorkers=4
to--maxWorkers=3
.This likely resolved the issue because we stopped overloading CI box CPU ā prior to the change we would spin up up to 4 jest parent processes per box, resulting in a total of
20
processes being spun up at one time (4 parent processes + 4 workers per parent * 4) --> By reducing maxWorkers to 3 the maximum total number of jest processes running on any give box is now16
and the timeouts seem to have been resolved.I donāt know if could maybe help someone here, but I had the same problem and recently solved it.
I had a
test:unit
script in my projectās package.json that run:jest --detectOpenHandles --logHeapUsage --colors -c jest.config.js
.My 195 tests consumed a total of about 1800MB heap size (starting from 200MB) and took more than 30s to complete.
The solution in my case was just removing the
--detectOpenHandles
option and I can now run my tests within 10s with max 580MB by test (no more incremental consumption).@SimenB
--workerIdleMemoryLimit
option is not working when--detectOpenHandles
option is enabled@Kle0s setting
maxWorkers:1
doesnāt work, because jest automatically detects that and runs inband. so you need the runner override (this.isSerial = false;
) from @a88zach to get it working.I went through all the messages here and tried their suggestions. I started out with much slower testing than before I upgraded Jest and a crash at the given memory limit. But with this command line things are back to normal (full speed, heap mem < 1GB, 200K+ LOC, Typescript vite app):
But I had to use both
--no-compilation-cache
and--expose-gc
to make it work. Node version is 19.3.0 and Jest version is 29.3.1.Memory running rampant after a while is something I even run into with 16.10 to some degree. The only way Iāve found to prevent this build-up is by finding a good combination of workers & memory limit. The sweet spot on my machine (M1 w/ 16gb) is running our 9k tests with 6 workers and
--max-old-space-size=768
, which now gives me a rock-solid 140-145s execution time. Any more workers or memory and I get memory pressure and very unstable execution times, any less workers or memory and execution time goes up considerably. Without memory limit Jest is simply unusable at this point.The same approach seems to work when I run it on the latest node (18.6), however, execution times increase to 175-180s.
I also noticed that a considerable part of the memory is used to cache hundreds of copies of identical external import sources (with react-dom sticking out quite a bit). Not sure this is normal behavior.
Sadly the above runtime didnāt solve the issue for us. This is 16.10.0 compared against 18.4.0
Sadly I think if we canāt find a proper solution to this we may have to drop jest, this essentially has us pegged at Node 16.10.0 which isnāt really a viable position to be in long term.
I went through the commits and looked for code relating to node as opposed to the v8 upgrade and I cannot find anything. I doubt this will get fixed unless its someone on the node or v8 team helping.
I also tried the class static initialisers from the blogpost (https://v8.dev/blog/v8-release-94) and also no luck in finding a cause.
node 18 + jest 27 works well (few leaks) node 18 + jest 28 a lot of leaks
Yes
@SimenB would you accept a PR to lower v16 in engines to 16.10?
@alumni I would rather not write a test runner - I feel like there would be problems with changedSince and watch mode and also the performance would depend on how many files each spec file imported - for some projects your approach wonāt be slower, for others (like mine) I suspect it would.
I have performed some investigation relating to the bug noted above in Node and couldnāt observe any noticable difference between Node 16.10.X and node 16.11.X, and that specific issue existed back to Node V14.5.0. So while it is likely to be causing an issue in Jest I donāt think itās quite as related as we think.
@mb21 just looked, my current workaround for Node >
16.10
itās to tweakmax-old-space-size
, with your reproduce:Node
16.13.1
https://gist.github.com/pustovalov/c98f3f521943aa082a2edbc70a343302#file-1-log
https://gist.github.com/pustovalov/c98f3f521943aa082a2edbc70a343302#file-2-log
In my case https://github.com/facebook/jest/issues/11956#issuecomment-969041221, limit in
700mb
works fine for:Also the execution time increased
Node v14.17.3
Node v16.13.0
Iāve tried that. I was able to reproduce the issue on the last commit on v8 upgrade. It looks like the problem is related to v8 upgrade inside of node.
Weāre also unable to update to the LTS version of Node 16 (at the time of writing 16.13.0) because of this issue. We bisected the changes and identified that the upgrade from Node 16.10 to 16.11 caused our large Jest suite to hang indefinitely.
I took a look at the Node 16.11 changelog and I think the most likely culprit for this issue comes from the V8 update to 9.4 (PR). In V8 9.4, the new Sparkplug compiler is enabled by default (see also this Node issue).
I was hoping I could try disabling sparkplug to see verify that this is the issue.
node
exposes a V8 option to disable (--no-sparkplug
), but I donāt think itās passing through to the Jest workers when I call it like this:I also tried setting the V8 option in
jest-environment-node
here: https://github.com/facebook/jest/blob/42b020f2931ac04820521cc8037b7c430eb2fa2f/packages/jest-environment-node/src/index.ts#L109 viabut I didnāt see any change. Iām not sure if that means Sparkplug isnāt causing the problem or if Iām not setting the V8 flag properly in the jest workers.
@SimenB - I see youāve committed a good deal to
jest-environment-node
- any tips for how I might pass that V8 flag down through all the workers? If itās possible (even viapatch-pacakge
or something) Iād be happy to give it a shot on our test suite thatās exhibiting the problem.So Iām not exactly positive that this is the cause of the issue, but it seems like a potentially promising place to start looking.
Hi, Iām having the same problem but as far as I understand, version 20.11.0 of node should have this fixed?
This addressed our issue as well with Jest constantly using more memory between tests. Unfortunately could not use
workerIdleMemoryLimit
setting as our runners are using Linux where memory isnāt reported correctly.Iām curious as to what the fix is, Iāve looked at the release and cannot find the commit that addresses this. It should be way more notable in the release notes that this was fixed
https://nodejs.org/en/blog/release/v21.1.0
Either way, very thankful this has been addressed after 2 years. Hoping for a backport to 20 and 18. We need this fix in an LTS version.
Thatās number 4 here https://github.com/jestjs/jest/pull/12205#issuecomment-1749113564. Tl;dr - upstream issue in V8, a standalone reproduction using just node apiās is probably needed before people working on node dig into it.
But that is orthogonal to this issue which is about the regression in same code from node 16.10 to 16.11.
node 20.8 is released now with a fix for the memory leak. i didnāt check it in detail to know if jest also have to change anything
Now that https://github.com/nodejs/node/pull/48510 has just been closed. On what Node version this issue will be fix?
Can anyone confirm if this problem persists using Node 18.15.0?
We are trying to update to node 18 and tried the workerIdleMemoryLimit but didnāt seem to make a difference in our case. Still a good 30% increase in overall test execution and increased flaky test failures.
We donāt want to push back our upgrade to node 18 much longer so we started looking at alternative test runners. Might pin to node 16.10 to run tests, but itās not a viable long term solution.
Iām trying to go over the various other work around posted in this thread and will post back if I find one suitable for our scenario.
The transpiration is not the cause as I tried to compile the tests and run the compiled ones but it didnāt help
for github actions you need to set workerIdleMemoryLimit toi lower than 2GB, because there is a strange heap limit of 2GB
I can also confirm that adding a specified
workerIdleMemoryLimit
solves the issue for our test suite! However, I noticed that using relative units did not work for our case. Weāre running on a CircleCI machine with 16 GB RAM and got the following results: Edit: Forgot to mention: weāre using 8 workers on a 8 CPU machine0.15: ā times out after 10 minutes, 100% RAM usage recorded ā10%ā: ā times out after 10 minutes, 100% RAM usage recorded ā200MBā: ā after 8 minutes ā800MBā: ā after 4 minutes ā1600MBā: ā after 2 minutes (peak RAM usage: 64%)
Iād expect to get similar results using 1600 MB and 10%, if my Math is correct, so maybe somethingās off with the calculation logic (my tests are not scientific though, just single data points I took)?
But anyway, I appreciate you adding this new flag very much! It finally gives us a way to work around the memory issue, which unblocks us from upgrading NodeJS šāāļø
@alaughlin - see https://github.com/facebook/jest/pull/12205#discussion_r815307518
Looks like a dead end so far on supporting ESM with this custom runtime
@a88zach - thank you for this! The runner youāve published enabled us to upgrade past Node 16.10 with minimal memory/runtime impact.
https://github.com/facebook/jest/releases/tag/v28.0.2
PR created to allow 16.10 - https://github.com/facebook/jest/pull/12754
You could use
--shard
and do the parallelization yourself rather than letting jest handle it. We do something like this with v27 (we spawn parallel jest processes that only handle a small amount of tests) and itās much much faster and has a much smaller memory usage. I donāt have exact numbers, but we get OOM after 30-40 min (only about half of the tests have been run) if we run the entire test suite, while with this approach weāre done in 7-8 min.FWIW, applying this patch makes itās possible to us to run jest setup on node 16.14.2. Heap size stays ~200MB per test but without that patch they are going over 2 GB. Which makes its impossible to run the test setup.
Our test setup is testing Angular app so it may be that the patch is only making difference in certain kind of setups but not with all.
Full test setup run:
Since jest is not directly tied to node.js version Iām sure there is a huge number of various configurations out there and updating jest doesnāt necessarily happen at the same time you update node. That being said and mainly because the
new Script
vscompileFunction
has already gone back-and-forth I would propose to at least keep the code for both under a flag/setting. It would allow to more easily switch between the 2 strategies to determine which one is best suited for the use case.compileFunction
should probably be the default setting since it is technically better in latest node version, but might not stay like this since it looks more like a bug on node/v8 part.Personally, running our test suite takes a long time and thus Iāll aim to keep the fastest configuration as long as I can. I can always patch-package it if needed (Iāve done it before).
Thanks for the numbers, @Cellule.
I imagine āmostā 16.x will want to be on latest and not 16.10.
Based on the numbers and considering that
compileFunction
should not be a breaking change (except not being able to implementimportModuleDynamically
for esm, see skipped test), it seems reasonable to mainline the proposed change. WDYT?Looks like I am hitting this as well. It happens only when doing coverage (as it eats more memory), and it ends up with hard failure. Locking node version to 16.10 helps, node 17 fails the same. Fails locally too.
Maybe one interesting note, it started to happen after I upgraded node-mongodb to v4, failed pipeline here.
@blimmer nothing in particular is notable about our codebase. Itās not a small codebase, but not very big either. Prior to 16.11, there were known memory leaks when running test suite (potentially around lodash); maybe made worse by 16.11.
https://jestjs.io/docs/cli#--runinband
dāoh! Just realized GitHub is auto-referencing my commit notes pointing to many of yāallās advice here. Sorry! I did not mean to clutter this long discussion.
So that I contribute something useful, here is what I was able to use to get node 18 + Jest 29 working with GitHub Actions:
node version:
18.16.0
jest version:29.7.0
node 18 flags:
jest arguments:
Iām not seeing a huge slow down in performance, but our tests were a slog even before we switched to node 18 from v14.
Hi, With the latest fixes happened in node it fixes the memory issue but the perf issue still remains. Is that understanding correct? If yes any idea about plans for perf fix and who is supposed to make that fix node or chromium?
There goes @SimenB bursting our bubbles again š. So realistically, does that mean that thereās not really anything in an upstream pipeline that could alleviate this issue?
I tried using brew (
brew install node --HEAD
) it does not seem to fix the issue vs node 16.10 (~20s in 16.10 vs ~60s with the latest commit2dfaee4bc8
of node). We do not useexperimental-vm-modules
.I doubt that PR will help much for people not using
--experimental-vm-modules
. It only deals with native ESM leaking - it doesnāt do anything with code caching (which is the main issue here).That said, Iād be happy to be proven wrong! Itāll be available as a nightly tomorrow if people want to test it: https://nodejs.org/download/nightly/
For people on arm64 macs (m1 or m2), you can use the binary linked from https://github.com/nodejs/node/pull/48510#issuecomment-1717670338 if you donāt wanna wait ~15 hours for the nightly to be built.
This solution fixes the memory issue but the speed is still 2x slower. This is blocked us on node 16.10 forever. is there any solution to it
https://github.com/reside-eng/jest-runtime has fixed this issue for us. We no longer need
--workerIdleLimit=2GB
to keep our CI containers from crashing, and memory usage is flat instead of constantly growing. Surprised not to see this already mentioned in this thread, I must have found it in one of the many other related ones.Notably,
--expose-gc --no-compilation-cache
did not fix the runaway memory usage for us.Same here, using
node --expose-gc --no-compilation-cache
fixes the issue.Nice!! This -no-compilation-cache fix it for me. I was having a heap size over 2gb, with every test leaking memory to the next. With this config the leakage stopped and the final heap usage was 200mb. And no effect on speed for me also. Tks so much!
Hi @GBPROB2 , With this we did see memory reduction but the time it took to execute test suite is still degraded compared to 16.10.0 (3x more ) Iāll look into the thread for fix in node
In a complex codebase using Angular and ESM modules in Node 16.18.1, the only alternative that brought memory relief for me was:
500MB
)Unfortunately, all the other possibilities (
isolateModules,
--no-compilation-cache,
etc.) didnāt work for me. I am still fixing some 60 tests that broke because of the upgrade, but the above technique eliminated the pesky OOM issues for a 4,600+ test suite previously running Jest 27.5.1.anybody tested with node 20 ?
I was noticing that GitHub Actions was failing with OOM errors even before the test output starting logging (Jest + ts-jest + Vue SFCs). For me, the issue actually seemed to be related to the default
babel
code coverage provider (see https://github.com/jestjs/jest/issues/5837). Switching tov8
resolved the OOM issue:https://jestjs.io/docs/configuration#coverageprovider-string
Due to potential security risk if pinning down to Node 16.10, our team decides to move on with Node 18.x with a workaround: Split test cases into multiple waves, using testPathPattern and testPathIgnorePatterns options. Partition by folder. There would be fewer test cases in each wave so it can finish before running out of memory.
Test cases in each wave can be a parallel run, although only one wave is running at a time. This takes 50% more time to run, but still 2x faster than the workaround above (all sequential).
FWIW I used
-w=4 --workerIdleMemoryLimit=1.5G
on a CircleCIāslarge
docker runner, this works fine for us.large
on CircleCI means 4CPU and 8GB of memory.Thank you @a88zach, your explanation makes a lot of sense. I applied the suggestion and it worked for me.
I used @a88zachās solution and while the rate of the process crashing has went down significantly, it still happens occasionally. Since it occurs in CI during the tests stage, it happened a few times out of ~50 runs today.
Iām still hoping someone at facebook will take care of that or something š
@a88zach your solution is working perfectly for me. I can finally switch to
node@18
. Thanks for sharing šI tried this solution now @a88zach and it did not work for me. I will state however, that I simply set the
maxWorkers
to 1 in thejest.config.js
file. I did check if it was properly parsed by passing to jest the--debug
switch. In the debug print I got:I still get the following error in the CI:
@SimenB Iām not sure if as a maintainer youāre able to edit the original issue. If not perhaps an alternative plan is to close and lock this issue so my comment remains at the bottom as the workaround and we open a new issue referencing this one to continue tracking the problem. Hopefully itāll stop people wasting effort on workarounds that already exist and limit focus to a solution to the underlying issue (which I believe to be a V8 vm issue anyway).
@ckcr4lyf Iām honestly not sure where the issue(s) might lie. Iāve not really used the node inspector before or spent any time digging around in node heap profiles, so Iām not sure if the conclusions Iām drawing are reasonable, but Iāve posted several updates in the ts-jest thread I mentioned earlier, but Iāll summarise here.
I spent most of today capturing heap profiles and delving into the code of various dependencies. The pattern Iām seeing is an ~180MB heap allocation happening every time jest advances a test suite (all testing is being done with
--runInBand
). Within these 180MB heap allocations, ~100MB of that seems to be strings which look like node module source/(or maybe source maps?). 100MB is coincidently roughly the amount Iām seeing the heap grow by for every test suite that runs.These strings looked like they were in an the
retrieveMapHanders
array withinsource-map-support
being retained byError.prepareStackTrace
. Within source-map-support I see an implementation ofprepareStackTrace
which gets monkey patched onto Error withininstall
.Checking my yarn.lock for dependencies on source-map-support, the only dependency I found was jest-runner. Sure enough, I found that runTest.ts calls install from support-source-map (twice)
later in
runTestInternal
I see the cleanup function from source-map-support (resetRetrieveHandlers) is called, but only once for the ālocalā import of sourcemapSupport.I commented these lines in my local runTest.js and reprofiled, and whilst it didnāt reduce the memory usage by much, the retaining objects on the strings I saw were now applicationinsights diagnosticchannel monkey patching.
I stubbed out the couple of places I was importing āapplicationinsightsā and reprofiled and found that again, whilst the memory usage hadnāt changed much, the strings were now being retained by closures from
Node:timers
due tonode-cache
which is another dependency I have.In node-cache
_checkData
I can see a timeout is being set with a recursive callback to itself. This is the function shown in the heap stack. There is a_killCheckPeriod
which the comment mentions is to clear the timeout so for testing purposes, and this_killCheckPeriod
is called in two places,close
andflushall
.In my tests I am calling
close
inafterAll
in my setup filed specified insetupFilesAfterEnv
, so I would have hoped that would be unsetting the timeoutā¦In all instances, I can see in the stack trace that the array containing the strings is ultimately either
_moduleRegistry
of_cacheFS
both of which are on the jest-runtimeRuntime
object. jest-runner callstearDownEnv
which in turn callsteardown
on theRuntime
. This calls clear on the various internal maps and arrays like_moduleRegistry
and_cacheFS
.Iām not familiar with node (or jest) internals to know what conclusion can be drawn from all this, or where the issue really lies. Iām not sure if these allocations represent the āleakā that Iām seeing in terms of heap usage growing, but if they are, then my assumption is that modules within the test runtime are being retained after
tearDownEnv
is called and that it is these retained modules that represent the heap growth. If this is the case the leak is proportional to the size and number of imports a test has, and so will grow with every test that has any such import that retains a reference to _moduleRegistry/_cacheFS.In my case, I am using supertest to test an express app, which means my dependencies are always the same, and always include the various offenders (applicationinsights, node-cache etc), which would explain why the leak Iām seeing seems to scale linearly with number of tests.
Next thing I wanted to try was removing use of node-cache and reprofiling to see how that looks.
Immediately tried @brokenmass 's solution above andā¦ it gave amazing results!
I needed to modify the import like this (otherwise it gave an error):
Previously, my ~2000 tests on Node 16.10 took 20 seconds. With the workaround I was able to run it on Node 18.21.1 in just 30 seconds (with memory limit ~800Mb, which was optimal after few tries in the range of 500Mb to 1Gb).
Without the workaround on the same Node 18.21.1 it takesā¦ 85 seconds. (and makes my machine almost unusable, taking all the memory, swap, etc).
Thanks!
This elegant fix may unlock us to actually go beyond Node 16.10 š
@SebTVisage you need to run my fix along with
--logHeapUsage
but without--runInBand
to provide any useful diagnostics.--runInBand
preventsworkerIdleMemoryLimit
from doing anything.Thank you very much for your investigation and @phawxby š.
I tried it with different values from 250MB to 1500MB (running in a Github actions), but unfortunately it doesnāt work for our monorepo. It does work locally. But before migrating to node 16 (we were blocked on node 12) everything was fine on the CI as well.
The only think I can think of is that weāre using ts-jest, and in memory mongo to spin up a mongo instance per test file. Do you see any reason why your workaround wouldnāt work with this?
Nope, not in our project. I did spend some time when I initially started investigating this trying to narrow down which modules were causing the issue but with no luck.
Ideally weād use
compileFunction
, we went back toScript
due to other performance issues with it šBut yes, this is really a bug in Node (or rather, v8) that weāre trying to work around up until itās fixed upstream
I tested the
workerIdleMemoryLimit
and itās at least usable with newer versions of Node.However, it doesnāt feel like it really solves the problem. Using the runner published as part of this PR: https://github.com/facebook/jest/pull/12205#issuecomment-1150255655 doesnāt require worker restarts because it fixes the memory leak altogether in our application.
When running with
--logHeapUsage
andside@jest-runtime
(https://github.com/facebook/jest/pull/12205) I see the memory usage hovering right around 200-300 megabytes for all tests.However, with the
alpha.5
version, I see the heap grow until the worker is restarted at theworkerIdleMemoryLimit
value I set.Thatās the behavior I expected from that PR and new setting, but I wanted to call out that I think weāve got the real fix in #12205.
yes, sorry. my fault. š
For us too
but for
@jest/test-result
onlyv29.0.0-alpha.4
is available, so it pulls some other jestv29.0.0-alpha.4
depsI confirm the latest alpha version works flawlessly for us! Thanks for the fix @phawxby @SimenB
Might be
totalmem()
not working on circle ci. Itās notoriously hard to get proper available hw there (e.g.cpus()
is also completely broken, reporting 64 when thereās 2 or 4). I put together https://github.com/SimenB/available-cpu at some point to see, maybe something for memory should be done as well?Our team also began hitting this when attempting to upgrade from Node v14 to Node v16.14.2 in https://github.com/microsoft/accessibility-insights-web/pull/5343. @karanbirsingh recorded some experiments with different node/v8 settings in this comment.
Our repo had previously been using
--max-old-space-size=16384
with our Jest tests (an old workround for an unrelated issue in Azure DevOps which we think is obsolete). This seemed to exacerbate the issue significantly; we found that removing this tripled the speed of our unit test suite (though it still didnāt get it down to Node <=16.10.0 speeds).My findings do not match the node bug also: https://github.com/nodejs/node/issues/40014 so I 100% agree with @phawxbyās comment here: https://github.com/facebook/jest/issues/11956#issuecomment-1077523582
something went in between v16.10.0 and v16.11.0 which broke jest and although the node bug seems related, if it is, it must be that something now makes the bug trigger in v16.11.0
v14.4.0 is ok v14.5.0 is ok v14.19.0 is ok
v16.10.0 is ok v16.11.0 is broken
v18.0.0 is broken.
I just discovered jest now requires node 16.13 - so I cannot stay on node 16.10 to avoid this bug any more š
For the dummy tests from the examples above I actually got a slight increase in time. Without using the 2 node flags I got time ~3.8s, with the flags I got ~4.7s. But for the actual tests from my codebase ( 36 test suits 178 tests ) the time has actually decreased from ~65s to ~46s. Tested locally on mac with
node v16.11.0
,"jest": "^27.3.1"
and"ts-jest": "^27.0.7",
@lukeapage thanks for pointing out the difference š , I lost the details in the way and thought that was not being investigated anymore
@pustovalov these are the results from running:
stdout:
Memory usage log:
jest_memory_usage.log
Thanks you both a lot for taking a look into it š
EDIT: I ran this with Node
16.13.0
@amiedes can you try to run jest in this way?
@amiedes it looks to me like the v8 bug was closed as wonāt fix but the nodejs bug is still open.
Iām just a interested spectator and donāt have a 100% understanding but it seems to me like no one has indicated a belief this is a jest bug.