I’m running a large test suite with Jest (26.6.2
) and react-testing-library (12.1.2
) on Node.js (16.3.1
).
At some point the test suite sometimes crashes with
FATAL ERROR: CALL_AND_RETRY_LAST Allocation failed - JavaScript heap out of memory
The first idea is that the Node.js heap is full. Thus, I’ve set a heap size of 2GB via --max-old-space-size=2048
and also enabled GC logging (Node’s --expose-gc
+ Jest’s --logHeapUsage
).
The GC logging indicates that the heap space is probably not the problem (if my understanding is correct, the error message is at least misleading):
<--- Last few GCs --->
[206149:0x4b56570] 1113100 ms: Mark-sweep (reduce) 872.1 (1175.1) -> 871.2 (1114.6) MB, 1786.2 / 0.0 ms (average mu = 0.018, current mu = 0.014) last resort GC in old space requested
[206149:0x4b56570] 1114841 ms: Mark-sweep (reduce) 871.2 (1114.6) -> 871.2 (1091.4) MB, 1740.9 / 0.0 ms (average mu = 0.009, current mu = 0.000) last resort GC in old space requested
<--- JS stacktrace --->
FATAL ERROR: CALL_AND_RETRY_LAST Allocation failed - JavaScript heap out of memory
1: 0xb00d90 node::Abort() [./.gradle/nodejs/node-v16.13.1-linux-x64/bin/node]
2: 0xa1823b node::FatalError(char const*, char const*) [./.gradle/nodejs/node-v16.13.1-linux-x64/bin/node]
3: 0xcedbce v8::Utils::ReportOOMFailure(v8::internal::Isolate*, char const*, bool) [./.gradle/nodejs/node-v16.13.1-linux-x64/bin/node]
4: 0xcedf47 v8::internal::V8::FatalProcessOutOfMemory(v8::internal::Isolate*, char const*, bool) [./.gradle/nodejs/node-v16.13.1-linux-x64/bin/node]
5: 0xea6105 [./.gradle/nodejs/node-v16.13.1-linux-x64/bin/node]
6: 0xeb8588 v8::internal::Heap::AllocateRawWithRetryOrFailSlowPath(int, v8::internal::AllocationType, v8::internal::AllocationOrigin, v8::internal::AllocationAlignment) [./.gradle/nodejs/node-v16.13.1-linux-x64/bin/node]
7: 0xe85b76 v8::internal::Factory::CodeBuilder::AllocateCode(bool) [./.gradle/nodejs/node-v16.13.1-linux-x64/bin/node]
8: 0xe86885 v8::internal::Factory::CodeBuilder::BuildInternal(bool) [./.gradle/nodejs/node-v16.13.1-linux-x64/bin/node]
9: 0xe8729e v8::internal::Factory::CodeBuilder::Build() [./.gradle/nodejs/node-v16.13.1-linux-x64/bin/node]
10: 0x14d69fe v8::internal::RegExpMacroAssemblerX64::GetCode(v8::internal::Handle<v8::internal::String>) [./.gradle/nodejs/node-v16.13.1-linux-x64/bin/node]
11: 0x11c1329 v8::internal::RegExpCompiler::Assemble(v8::internal::Isolate*, v8::internal::RegExpMacroAssembler*, v8::internal::RegExpNode*, int, v8::internal::Handle<v8::internal::String>) [./.gradle/nodejs/node-v16.13.1-linux-x64/bin/node]
12: 0x11df6b7 v8::internal::RegExpImpl::Compile(v8::internal::Isolate*, v8::internal::Zone*, v8::internal::RegExpCompileData*, v8::base::Flags<v8::internal::JSRegExp::Flag, int>, v8::internal::Handle<v8::internal::String>, v8::internal::Handle<v8::internal::String>, bool, unsigned int&) [./.gradle/nodejs/node-v16.13.1-linux-x64/bin/node]
13: 0x11dfe60 v8::internal::RegExpImpl::CompileIrregexp(v8::internal::Isolate*, v8::internal::Handle<v8::internal::JSRegExp>, v8::internal::Handle<v8::internal::String>, bool) [./.gradle/nodejs/node-v16.13.1-linux-x64/bin/node]
14: 0x11e0a5e v8::internal::RegExpImpl::IrregexpPrepare(v8::internal::Isolate*, v8::internal::Handle<v8::internal::JSRegExp>, v8::internal::Handle<v8::internal::String>) [./.gradle/nodejs/node-v16.13.1-linux-x64/bin/node]
15: 0x11e0bd7 v8::internal::RegExpImpl::IrregexpExec(v8::internal::Isolate*, v8::internal::Handle<v8::internal::JSRegExp>, v8::internal::Handle<v8::internal::String>, int, v8::internal::Handle<v8::internal::RegExpMatchInfo>, v8::internal::RegExp::ExecQuirks) [./.gradle/nodejs/node-v16.13.1-linux-x64/bin/node]
16: 0x1205da8 v8::internal::Runtime_RegExpExec(int, unsigned long*, v8::internal::Isolate*) [./.gradle/nodejs/node-v16.13.1-linux-x64/bin/node]
17: 0x15e7819 [./.gradle/nodejs/node-v16.13.1-linux-x64/bin/node]
The tests crash at different points (even different test classes!) but I always see a RegExp
compilation in the JS stacktrace. Searching for the stacktrace pattern leads me to https://bugs.chromium.org/p/v8/issues/detail?id=10441#c5 which gives the hint that the V8 code_space
memory region could be the problem:
V8 has limit of 128*MB for code objects. Each RegExp.exec (or String.match) compiles an expression and thus creates a code object, which may be large depending on the RegExp.
Indeed, when I add logging of the V8 heap space statistics via v8.getHeapSpaceStatistics()
, I can see that the code_space
region could be causing the crashes.
The last output I see before the crash is:
Heap statistics: {
"total_heap_size": 1251897344,
"total_heap_size_executable": 133742592,
"total_physical_size": 1249687440,
"total_available_size": 1200419000,
"used_heap_size": 968511640,
"heap_size_limit": 2197815296,
"malloced_memory": 4202568,
"peak_malloced_memory": 12083656,
"does_zap_garbage": 0,
"number_of_native_contexts": 3,
"number_of_detached_contexts": 0
}
Heap space statistics: [
[…]
{
"space_name": "old_space",
"space_size": 1011437568,
"space_used_size": 766010912,
"space_available_size": 226275256,
"physical_space_size": 1011428824
},
{
"space_name": "code_space",
"space_size": 133275648,
"space_used_size": 124423840,
"space_available_size": 512352,
"physical_space_size": 131074880
},
[…]
]
I’m wondering if the crash suggests that the test suite is just too large or if it is rather a memory leak?
Is there a way to increase the code_space
region size in V8?
I’m appreciating any hint to narrow down the root cause for those crashes.
RegExp.prototype.exec
with your own function that outputs the RegExp to the console and then calls the original one – OrchardRegExp.prototype.exec
. – Traceable