You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
{{ message }}
Repository navigation
SourceTextModule leaks memory when context is used #50113
import{SourceTextModule,SyntheticModule,createContext}from'vm';asyncfunctionlink(){// Having a shared context leaks nodejs memoryletcontext=createContext()constmodule=newSourceTextModule(` import foo from 'foo'; export default {a: new Array(100000).fill('-')}; `,{
context,identifier: ''});constsubModule=newSourceTextModule(` import asd from 'asd.json'; export default asd; `,{
context,identifier: 'foo'})constjsonModule=newSyntheticModule(['default'],function(){// mimic some large filethis.setExport('default',{a: newArray(100000).fill('-')});},{
context,identifier: 'asd.json'})awaitsubModule.link(()=>jsonModule)awaitmodule.link(()=>subModule)awaitmodule.evaluate();// UN-COMMENT THIS LINE TO FIX THE LEAK?? wtf...context=nullreturnmodule;}for(leti=0;i<100000;i++){if(i%100===0){console.log("iterations"+i)}awaitlink();}
How often does it reproduce? Is there a required condition?
100%
What is the expected behavior? Why is that the expected behavior?
NodeJs should not leak memory because the context object goes out of scope and should get garbage collected and all the modules associated with it.
What do you see instead?
The memory grows and grows depending on a number of modules and size of their data until nodejs fails to OOM.
Additional information
Note: Manually setting the context object null in JS side fixes the leak, but that should not be required
Hmm after further testing it seems that setting context to null doesnt fully fix the issue, but it does not go OOM every time I run the program, but more like 90% of the time? Or maybe there is some flakiness in the test case
From a quick glance I think it's the resolve cache causing the leak, you don't actually need the context, it just causes the process to crash sooner because contexts are big and add more pressure, you can still reproduce the OOM with --max-old-space-size=16 even if you remove the context.
Off the top of my head, moving the resolve cache to JS land may fix the leak (I don't actually know if it's feasible to move it to JS land, however).
I compiled NodeJS from sources with --debug-node --debug --v8-non-optimized-debug configuration arguments defined and downloaded valgrind to track the origin of the memory leak. Then I run valgrind with nodejs for that script using command:
If we ignore all the memory leak errors and warnings before 2000 iterations of link -method (just to get rid off node initialization leaks). Valgrind reports following errors. See attachment. leak-log.txt
edit: ah... Seems I need to provide --num-callers=500 argument to get full call stack... I will try again
==404163== Conditional jump or move depends on uninitialised value(s)
==404163== at 0x18B44EB: v8::internal::Heap::IncrementalMarkingLimitReached() (in /home/sampo/git/node/out/Release/node)
==404163== by 0x18BFB17: v8::internal::Heap::StartIncrementalMarkingIfAllocationLimitIsReached(v8::base::Flags<v8::internal::GCFlag, unsigned char, unsigned char>, v8::GCCallbackFlags) (in /home/sampo/git/node/out/Release/node)
==404163== by 0x18CCCD1: v8::internal::Heap::CollectGarbage(v8::internal::AllocationSpace, v8::internal::GarbageCollectionReason, v8::GCCallbackFlags) (in /home/sampo/git/node/out/Release/node)
==404163== by 0x18A1EA6: v8::internal::HeapAllocator::AllocateRawWithLightRetrySlowPath(int, v8::internal::AllocationType, v8::internal::AllocationOrigin, v8::internal::AllocationAlignment) (in /home/sampo/git/node/out/Release/node)
==404163== by 0x18A3274: v8::internal::HeapAllocator::AllocateRawWithRetryOrFailSlowPath(int, v8::internal::AllocationType, v8::internal::AllocationOrigin, v8::internal::AllocationAlignment) (in /home/sampo/git/node/out/Release/node)
==404163== by 0x187FD1A: v8::internal::Factory::NewFillerObject(int, v8::internal::AllocationAlignment, v8::internal::AllocationType, v8::internal::AllocationOrigin) (in /home/sampo/git/node/out/Release/node)
==404163== by 0x1CF07BB: v8::internal::Runtime_AllocateInYoungGeneration(int, unsigned long*, v8::internal::Isolate*) (in /home/sampo/git/node/out/Release/node)
==404163== by 0x21BC3F5: Builtins_CEntry_Return1_ArgvOnStack_NoBuiltinExit (in /home/sampo/git/node/out/Release/node)
==404163== by 0x215627C: Builtins_ArraySingleArgumentConstructor_HoleySmi_DisableAllocationSites (in /home/sampo/git/node/out/Release/node)
==404163== by 0x212497B: Builtins_InterpreterPushArgsThenFastConstructFunction (in /home/sampo/git/node/out/Release/node)
==404163== by 0x229FD0A: Builtins_ConstructHandler (in /home/sampo/git/node/out/Release/node)
==404163== by 0x2123F17: Builtins_InterpreterEntryTrampoline (in /home/sampo/git/node/out/Release/node)
==404163== by 0x2163ACC: Builtins_GeneratorPrototypeNext (in /home/sampo/git/node/out/Release/node)
==404163== by 0x2121B1B: Builtins_JSEntryTrampoline (in /home/sampo/git/node/out/Release/node)
==404163== by 0x2121842: Builtins_JSEntry (in /home/sampo/git/node/out/Release/node)
==404163== by 0x17EB52A: v8::internal::(anonymous namespace)::Invoke(v8::internal::Isolate*, v8::internal::(anonymous namespace)::InvokeParams const&) (in /home/sampo/git/node/out/Release/node)
==404163== by 0x17EC31F: v8::internal::(anonymous namespace)::InvokeWithTryCatch(v8::internal::Isolate*, v8::internal::(anonymous namespace)::InvokeParams const&) [clone .constprop.0] (in /home/sampo/git/node/out/Release/node)
==404163== by 0x17EC982: v8::internal::Execution::TryCall(v8::internal::Isolate*, v8::internal::Handle<v8::internal::Object>, v8::internal::Handle<v8::internal::Object>, int, v8::internal::Handle<v8::internal::Object>*, v8::internal::Execution::MessageHandling, v8::internal::MaybeHandle<v8::internal::Object>*, bool) (in /home/sampo/git/node/out/Release/node)
==404163== by 0x1BB99F5: v8::internal::SourceTextModule::ExecuteModule(v8::internal::Isolate*, v8::internal::Handle<v8::internal::SourceTextModule>) (in /home/sampo/git/node/out/Release/node)
==404163== by 0x1BB9F97: v8::internal::SourceTextModule::InnerModuleEvaluation(v8::internal::Isolate*, v8::internal::Handle<v8::internal::SourceTextModule>, v8::internal::ZoneForwardList<v8::internal::Handle<v8::internal::SourceTextModule> >*, unsigned int*) (in /home/sampo/git/node/out/Release/node)
==404163== by 0x1BBA1BB: v8::internal::SourceTextModule::Evaluate(v8::internal::Isolate*, v8::internal::Handle<v8::internal::SourceTextModule>) (in /home/sampo/git/node/out/Release/node)
==404163== by 0x168B857: v8::Module::Evaluate(v8::Local<v8::Context>) (in /home/sampo/git/node/out/Release/node)
==404163== by 0x1269FE6: node::loader::ModuleWrap::Evaluate(v8::FunctionCallbackInfo<v8::Value> const&)::{lambda()#1}::operator()() const (module_wrap.cc:384)
==404163== by 0x126A4EC: node::loader::ModuleWrap::Evaluate(v8::FunctionCallbackInfo<v8::Value> const&) (module_wrap.cc:400)
==404163== by 0x2125ADC: Builtins_CallApiCallbackGeneric (in /home/sampo/git/node/out/Release/node)
==404163== by 0x9C4AA64: ???
==404163== by 0x9C4AE65: ???
==404163== by 0x21609C2: Builtins_AsyncFunctionAwaitResolveClosure (in /home/sampo/git/node/out/Release/node)
==404163== by 0x222CD30: Builtins_PromiseFulfillReactionJob (in /home/sampo/git/node/out/Release/node)
==404163== by 0x214E4E7: Builtins_RunMicrotasks (in /home/sampo/git/node/out/Release/node)
==404163== by 0x2121A42: Builtins_JSRunMicrotasksEntry (in /home/sampo/git/node/out/Release/node)
==404163== by 0x17EB75C: v8::internal::(anonymous namespace)::Invoke(v8::internal::Isolate*, v8::internal::(anonymous namespace)::InvokeParams const&) (in /home/sampo/git/node/out/Release/node)
==404163== by 0x17ECABE: v8::internal::Execution::TryRunMicrotasks(v8::internal::Isolate*, v8::internal::MicrotaskQueue*) (in /home/sampo/git/node/out/Release/node)
==404163== by 0x18209C2: v8::internal::MicrotaskQueue::RunMicrotasks(v8::internal::Isolate*) (in /home/sampo/git/node/out/Release/node)
==404163== by 0x1820DAB: v8::internal::MicrotaskQueue::PerformCheckpoint(v8::Isolate*) (in /home/sampo/git/node/out/Release/node)
==404163== by 0x113916E: node::InternalCallbackScope::Close() (callback.cc:137)
==404163== by 0x1138E6F: node::InternalCallbackScope::~InternalCallbackScope() (callback.cc:92)
==404163== by 0x1301950: node::fs::FileHandle::CloseReq::Resolve() (node_file.cc:406)
==404163== by 0x13020E9: node::fs::FileHandle::ClosePromise()::{lambda(uv_fs_s*)#1}::operator()(uv_fs_s*) const (node_file.cc:496)
==404163== by 0x1302139: node::fs::FileHandle::ClosePromise()::{lambda(uv_fs_s*)#1}::_FUN(uv_fs_s*) (node_file.cc:498)
==404163== by 0x12EB8FF: node::MakeLibuvRequestCallback<uv_fs_s, void (*)(uv_fs_s*)>::Wrapper(uv_fs_s*) (req_wrap-inl.h:126)
==404163== by 0x21129CF: uv__poll_io_uring (linux.c:1108)
==404163== by 0x21129CF: uv__io_poll (linux.c:1422)
==404163== by 0x20FD616: uv_run (core.c:447)
==404163== by 0x113BD18: node::SpinEventLoopInternal(node::Environment*) (embed_helpers.cc:41)
==404163== by 0x1381988: node::NodeMainInstance::Run(node::ExitCode*, node::Environment*) (node_main_instance.cc:112)
==404163== by 0x1381842: node::NodeMainInstance::Run() (node_main_instance.cc:88)
==404163== by 0x1277C63: node::StartInternal(int, char**) (node.cc:1380)
==404163== by 0x1277D31: node::Start(int, char**) (node.cc:1387)
==404163== by 0x25FFB5F: main (node_main.cc:97)
==404163== Uninitialised value was created by a heap allocation
==404163== at 0x69EA848: malloc (vg_replace_malloc.c:431)
==404163== by 0x1D6A0DD: v8::internal::AllocAtLeastWithRetry(unsigned long) (in /home/sampo/git/node/out/Release/node)
==404163== by 0x1D6F273: v8::internal::AccountingAllocator::AllocateSegment(unsigned long, bool) (in /home/sampo/git/node/out/Release/node)
==404163== by 0x1D6F54D: v8::internal::Zone::Expand(unsigned long) (in /home/sampo/git/node/out/Release/node)
==404163== by 0x1BEE596: v8::internal::Parser::Parser(v8::internal::LocalIsolate*, v8::internal::ParseInfo*, v8::internal::Handle<v8::internal::Script>) (in /home/sampo/git/node/out/Release/node)
==404163== by 0x1C20084: v8::internal::parsing::ParseProgram(v8::internal::ParseInfo*, v8::internal::Handle<v8::internal::Script>, v8::internal::MaybeHandle<v8::internal::ScopeInfo>, v8::internal::Isolate*, v8::internal::parsing::ReportStatisticsMode) (in /home/sampo/git/node/out/Release/node)
==404163== by 0x1736D0F: v8::internal::(anonymous namespace)::CompileToplevel(v8::internal::ParseInfo*, v8::internal::Handle<v8::internal::Script>, v8::internal::MaybeHandle<v8::internal::ScopeInfo>, v8::internal::Isolate*, v8::internal::IsCompiledScope*) (in /home/sampo/git/node/out/Release/node)
==404163== by 0x173715C: v8::internal::(anonymous namespace)::CompileScriptOnMainThread(v8::internal::UnoptimizedCompileFlags, v8::internal::Handle<v8::internal::String>, v8::internal::ScriptDetails const&, v8::internal::NativesFlag, v8::Extension*, v8::internal::Isolate*, v8::internal::MaybeHandle<v8::internal::Script>, v8::internal::IsCompiledScope*, bool (*)(int, void*), void*) (in /home/sampo/git/node/out/Release/node)
==404163== by 0x1737467: v8::internal::(anonymous namespace)::GetSharedFunctionInfoForScriptImpl(v8::internal::Isolate*, v8::internal::Handle<v8::internal::String>, v8::internal::ScriptDetails const&, v8::Extension*, v8::internal::AlignedCachedData*, v8::internal::BackgroundDeserializeTask*, bool (*)(int, void*), void*, v8::ScriptCompiler::CompileOptions, v8::ScriptCompiler::NoCacheReason, v8::internal::NativesFlag) (in /home/sampo/git/node/out/Release/node)
==404163== by 0x17386DD: v8::internal::Compiler::GetSharedFunctionInfoForScript(v8::internal::Isolate*, v8::internal::Handle<v8::internal::String>, v8::internal::ScriptDetails const&, v8::ScriptCompiler::CompileOptions, v8::ScriptCompiler::NoCacheReason, v8::internal::NativesFlag) (in /home/sampo/git/node/out/Release/node)
==404163== by 0x168F91D: v8::ScriptCompiler::CompileUnboundInternal(v8::Isolate*, v8::ScriptCompiler::Source*, v8::ScriptCompiler::CompileOptions, v8::ScriptCompiler::NoCacheReason) [clone .part.0] (in /home/sampo/git/node/out/Release/node)
==404163== by 0x168FF98: v8::ScriptCompiler::CompileModule(v8::Isolate*, v8::ScriptCompiler::Source*, v8::ScriptCompiler::CompileOptions, v8::ScriptCompiler::NoCacheReason) (in /home/sampo/git/node/out/Release/node)
==404163== by 0x12690FC: node::loader::ModuleWrap::New(v8::FunctionCallbackInfo<v8::Value> const&) (module_wrap.cc:204)
==404163== by 0x16D75C2: v8::internal::MaybeHandle<v8::internal::Object> v8::internal::(anonymous namespace)::HandleApiCallHelper<true>(v8::internal::Isolate*, v8::internal::Handle<v8::internal::HeapObject>, v8::internal::Handle<v8::internal::FunctionTemplateInfo>, v8::internal::Handle<v8::internal::Object>, unsigned long*, int) [clone .isra.0] (in /home/sampo/git/node/out/Release/node)
==404163== by 0x16D7910: v8::internal::Builtin_HandleApiConstruct(int, unsigned long*, v8::internal::Isolate*) (in /home/sampo/git/node/out/Release/node)
==404163== by 0x21BC2F5: Builtins_CEntry_Return1_ArgvOnStack_BuiltinExit (in /home/sampo/git/node/out/Release/node)
==404163== by 0x2120ECE: Builtins_JSBuiltinsConstructStub (in /home/sampo/git/node/out/Release/node)
==404163== by 0x9C4E777: ???
==404163== by 0x2120D41: Builtins_JSConstructStubGeneric (in /home/sampo/git/node/out/Release/node)
==404163== by 0x9C4F27E: ???
==404163== by 0x2120D41: Builtins_JSConstructStubGeneric (in /home/sampo/git/node/out/Release/node)
==404163== by 0x9C4AD40: ???
==404163== by 0x9C4A1A7: ???
==404163== by 0x21609C2: Builtins_AsyncFunctionAwaitResolveClosure (in /home/sampo/git/node/out/Release/node)
==404163== by 0x222CD30: Builtins_PromiseFulfillReactionJob (in /home/sampo/git/node/out/Release/node)
==404163== by 0x214E4E7: Builtins_RunMicrotasks (in /home/sampo/git/node/out/Release/node)
==404163== by 0x2121A42: Builtins_JSRunMicrotasksEntry (in /home/sampo/git/node/out/Release/node)
==404163== by 0x17EB75C: v8::internal::(anonymous namespace)::Invoke(v8::internal::Isolate*, v8::internal::(anonymous namespace)::InvokeParams const&) (in /home/sampo/git/node/out/Release/node)
==404163== by 0x17ECABE: v8::internal::Execution::TryRunMicrotasks(v8::internal::Isolate*, v8::internal::MicrotaskQueue*) (in /home/sampo/git/node/out/Release/node)
==404163== by 0x18209C2: v8::internal::MicrotaskQueue::RunMicrotasks(v8::internal::Isolate*) (in /home/sampo/git/node/out/Release/node)
==404163== by 0x1820DAB: v8::internal::MicrotaskQueue::PerformCheckpoint(v8::Isolate*) (in /home/sampo/git/node/out/Release/node)
==404163== by 0x113916E: node::InternalCallbackScope::Close() (callback.cc:137)
==404163== by 0x1138E6F: node::InternalCallbackScope::~InternalCallbackScope() (callback.cc:92)
==404163== by 0x1301950: node::fs::FileHandle::CloseReq::Resolve() (node_file.cc:406)
==404163== by 0x13020E9: node::fs::FileHandle::ClosePromise()::{lambda(uv_fs_s*)#1}::operator()(uv_fs_s*) const (node_file.cc:496)
==404163== by 0x1302139: node::fs::FileHandle::ClosePromise()::{lambda(uv_fs_s*)#1}::_FUN(uv_fs_s*) (node_file.cc:498)
==404163== by 0x12EB8FF: node::MakeLibuvRequestCallback<uv_fs_s, void (*)(uv_fs_s*)>::Wrapper(uv_fs_s*) (req_wrap-inl.h:126)
==404163== by 0x21129CF: uv__poll_io_uring (linux.c:1108)
==404163== by 0x21129CF: uv__io_poll (linux.c:1422)
==404163== by 0x20FD616: uv_run (core.c:447)
==404163== by 0x113BD18: node::SpinEventLoopInternal(node::Environment*) (embed_helpers.cc:41)
==404163== by 0x1381988: node::NodeMainInstance::Run(node::ExitCode*, node::Environment*) (node_main_instance.cc:112)
==404163== by 0x1381842: node::NodeMainInstance::Run() (node_main_instance.cc:88)
==404163== by 0x1277C63: node::StartInternal(int, char**) (node.cc:1380)
==404163== by 0x1277D31: node::Start(int, char**) (node.cc:1387)
==404163== by 0x25FFB5F: main (node_main.cc:97)
It includes node::loader::ModuleWrap::New which sounds related and https://bytes.usc.edu/cs104/wiki/valgrind/Uninitialised value was created by a heap allocation -error message
Activity
Havunen commentedon Oct 10, 2023
Hmm after further testing it seems that setting context to null doesnt fully fix the issue, but it does not go OOM every time I run the program, but more like 90% of the time? Or maybe there is some flakiness in the test case
Havunen commentedon Oct 10, 2023
@joyeecheung is this something you could take a look at? I ran the test using latest nightly version which should include all the merged fixes.
joyeecheung commentedon Oct 11, 2023
From a quick glance I think it's the resolve cache causing the leak, you don't actually need the context, it just causes the process to crash sooner because contexts are big and add more pressure, you can still reproduce the OOM with
--max-old-space-size=16even if you remove the context.Off the top of my head, moving the resolve cache to JS land may fix the leak (I don't actually know if it's feasible to move it to JS land, however).
Havunen commentedon Oct 11, 2023
Yep even this goes OOM:
node --experimental-vm-modules ./test.jsI also tested with a single SyntheticModule but I believe that does not leak.
Single SourceTextModule however seems like it does??
Havunen commentedon Oct 11, 2023
I compiled NodeJS from sources with
--debug-node --debug --v8-non-optimized-debugconfiguration arguments defined and downloaded valgrind to track the origin of the memory leak. Then I run valgrind with nodejs for that script using command:If we ignore all the memory leak errors and warnings before 2000 iterations of
link-method (just to get rid off node initialization leaks). Valgrind reports following errors. See attachment.leak-log.txt
edit: ah... Seems I need to provide
--num-callers=500argument to get full call stack... I will try againHavunen commentedon Oct 11, 2023
test.js
command:
valgrind --num-callers=500 --error-limit=no --leak-check=full --track-origins=yes ~/git/node/out/Release/node --experimental-vm-modules test.js > log.txt 2>&1output (ignoring first 500 iterations)
log.zip
This error stack looks suspicious to me:
It includes
node::loader::ModuleWrap::Newwhich sounds related and https://bytes.usc.edu/cs104/wiki/valgrind/Uninitialised value was created by a heap allocation-error messagelegendecas commentedon Jul 10, 2025
The OOM can be reproduced even without
linkandevaluate:This crashes after around tens of iterations deterministically.
Submitting a new issue for this: #59118
src: clear all linked module caches once instantiated
SimenB commentedon Jul 29, 2025
🎉
src: clear all linked module caches once instantiated
src: clear all linked module caches once instantiated
src: clear all linked module caches once instantiated