Skip to content

Commit 1096913

Browse files
committed
src: clear all linked module caches once instantiated
There are two phases in module linking: link, and instantiate. These two operations are required to be separated to allow cyclic dependencies. `v8::Module::InstantiateModule` is only required to be invoked on the root module. The global references created by `ModuleWrap::Link` are only cleared at `ModuleWrap::Instantiate`. So the global references created for depended modules are usually not cleared because `ModuleWrap::Instantiate` is not invoked for each of depended modules, and caused memory leak. The change references the linked modules in an object internal slot. This is not an issue for Node.js ESM support as these modules can not be off-loaded. However, this could be outstanding for `vm.Module`. PR-URL: #59117 Fixes: #50113 Reviewed-By: Joyee Cheung <[email protected]> Reviewed-By: Matteo Collina <[email protected]>
1 parent f176416 commit 1096913

File tree

3 files changed

+115
-46
lines changed

3 files changed

+115
-46
lines changed

src/module_wrap.cc

Lines changed: 79 additions & 38 deletions
Original file line numberDiff line numberDiff line change
@@ -123,12 +123,23 @@ ModuleWrap::ModuleWrap(Realm* realm,
123123
object->SetInternalField(kSyntheticEvaluationStepsSlot,
124124
synthetic_evaluation_step);
125125
object->SetInternalField(kContextObjectSlot, context_object);
126+
object->SetInternalField(kLinkedRequestsSlot,
127+
v8::Undefined(realm->isolate()));
126128

127129
if (!synthetic_evaluation_step->IsUndefined()) {
128130
synthetic_ = true;
129131
}
130132
MakeWeak();
131133
module_.SetWeak();
134+
135+
HandleScope scope(realm->isolate());
136+
Local<Context> context = realm->context();
137+
Local<FixedArray> requests = module->GetModuleRequests();
138+
for (int i = 0; i < requests->Length(); i++) {
139+
ModuleCacheKey module_cache_key = ModuleCacheKey::From(
140+
context, requests->Get(context, i).As<ModuleRequest>());
141+
resolve_cache_[module_cache_key] = i;
142+
}
132143
}
133144

134145
ModuleWrap::~ModuleWrap() {
@@ -149,6 +160,30 @@ Local<Context> ModuleWrap::context() const {
149160
return obj.As<Object>()->GetCreationContextChecked();
150161
}
151162

163+
ModuleWrap* ModuleWrap::GetLinkedRequest(uint32_t index) {
164+
DCHECK(IsLinked());
165+
Isolate* isolate = env()->isolate();
166+
EscapableHandleScope scope(isolate);
167+
Local<Data> linked_requests_data =
168+
object()->GetInternalField(kLinkedRequestsSlot);
169+
DCHECK(linked_requests_data->IsValue() &&
170+
linked_requests_data.As<Value>()->IsArray());
171+
Local<Array> requests = linked_requests_data.As<Array>();
172+
173+
CHECK_LT(index, requests->Length());
174+
175+
Local<Value> module_value;
176+
if (!requests->Get(context(), index).ToLocal(&module_value)) {
177+
return nullptr;
178+
}
179+
CHECK(module_value->IsObject());
180+
Local<Object> module_object = module_value.As<Object>();
181+
182+
ModuleWrap* module_wrap;
183+
ASSIGN_OR_RETURN_UNWRAP(&module_wrap, module_object, nullptr);
184+
return module_wrap;
185+
}
186+
152187
ModuleWrap* ModuleWrap::GetFromModule(Environment* env,
153188
Local<Module> module) {
154189
auto range = env->hash_to_module_map.equal_range(module->GetIdentityHash());
@@ -542,34 +577,28 @@ void ModuleWrap::GetModuleRequests(const FunctionCallbackInfo<Value>& args) {
542577
void ModuleWrap::Link(const FunctionCallbackInfo<Value>& args) {
543578
Realm* realm = Realm::GetCurrent(args);
544579
Isolate* isolate = args.GetIsolate();
545-
Local<Context> context = realm->context();
546580

547581
ModuleWrap* dependent;
548582
ASSIGN_OR_RETURN_UNWRAP(&dependent, args.This());
549583

550584
CHECK_EQ(args.Length(), 1);
551585

586+
Local<Data> linked_requests =
587+
args.This()->GetInternalField(kLinkedRequestsSlot);
588+
if (linked_requests->IsValue() &&
589+
!linked_requests.As<Value>()->IsUndefined()) {
590+
// If the module is already linked, we should not link it again.
591+
THROW_ERR_VM_MODULE_LINK_FAILURE(realm->env(), "module is already linked");
592+
return;
593+
}
594+
552595
Local<FixedArray> requests =
553596
dependent->module_.Get(isolate)->GetModuleRequests();
554597
Local<Array> modules = args[0].As<Array>();
555598
CHECK_EQ(modules->Length(), static_cast<uint32_t>(requests->Length()));
556599

557-
std::vector<Global<Value>> modules_buffer;
558-
if (FromV8Array(context, modules, &modules_buffer).IsNothing()) {
559-
return;
560-
}
561-
562-
for (uint32_t i = 0; i < modules_buffer.size(); i++) {
563-
Local<Object> module_object = modules_buffer[i].Get(isolate).As<Object>();
564-
565-
CHECK(
566-
realm->isolate_data()->module_wrap_constructor_template()->HasInstance(
567-
module_object));
568-
569-
ModuleCacheKey module_cache_key = ModuleCacheKey::From(
570-
context, requests->Get(context, i).As<ModuleRequest>());
571-
dependent->resolve_cache_[module_cache_key].Reset(isolate, module_object);
572-
}
600+
args.This()->SetInternalField(kLinkedRequestsSlot, modules);
601+
dependent->linked_ = true;
573602
}
574603

575604
void ModuleWrap::Instantiate(const FunctionCallbackInfo<Value>& args) {
@@ -582,9 +611,6 @@ void ModuleWrap::Instantiate(const FunctionCallbackInfo<Value>& args) {
582611
TryCatchScope try_catch(realm->env());
583612
USE(module->InstantiateModule(context, ResolveModuleCallback));
584613

585-
// clear resolve cache on instantiate
586-
obj->resolve_cache_.clear();
587-
588614
if (try_catch.HasCaught() && !try_catch.HasTerminated()) {
589615
CHECK(!try_catch.Message().IsEmpty());
590616
CHECK(!try_catch.Exception().IsEmpty());
@@ -724,9 +750,6 @@ void ModuleWrap::InstantiateSync(const FunctionCallbackInfo<Value>& args) {
724750
TryCatchScope try_catch(env);
725751
USE(module->InstantiateModule(context, ResolveModuleCallback));
726752

727-
// clear resolve cache on instantiate
728-
obj->resolve_cache_.clear();
729-
730753
if (try_catch.HasCaught() && !try_catch.HasTerminated()) {
731754
CHECK(!try_catch.Message().IsEmpty());
732755
CHECK(!try_catch.Exception().IsEmpty());
@@ -933,45 +956,63 @@ void ModuleWrap::GetError(const FunctionCallbackInfo<Value>& args) {
933956
args.GetReturnValue().Set(module->GetException());
934957
}
935958

959+
// static
936960
MaybeLocal<Module> ModuleWrap::ResolveModuleCallback(
937961
Local<Context> context,
938962
Local<String> specifier,
939963
Local<FixedArray> import_attributes,
940964
Local<Module> referrer) {
965+
ModuleWrap* resolved_module;
966+
if (!ResolveModule(context, specifier, import_attributes, referrer)
967+
.To(&resolved_module)) {
968+
return {};
969+
}
970+
DCHECK_NOT_NULL(resolved_module);
971+
return resolved_module->module_.Get(context->GetIsolate());
972+
}
973+
974+
// static
975+
Maybe<ModuleWrap*> ModuleWrap::ResolveModule(
976+
Local<Context> context,
977+
Local<String> specifier,
978+
Local<FixedArray> import_attributes,
979+
Local<Module> referrer) {
941980
Isolate* isolate = context->GetIsolate();
942981
Environment* env = Environment::GetCurrent(context);
943982
if (env == nullptr) {
944983
THROW_ERR_EXECUTION_ENVIRONMENT_NOT_AVAILABLE(isolate);
945-
return MaybeLocal<Module>();
984+
return Nothing<ModuleWrap*>();
946985
}
986+
// Check that the referrer is not yet been instantiated.
987+
DCHECK(referrer->GetStatus() <= Module::kInstantiated);
947988

948989
ModuleCacheKey cache_key =
949990
ModuleCacheKey::From(context, specifier, import_attributes);
950991

951-
ModuleWrap* dependent = GetFromModule(env, referrer);
992+
ModuleWrap* dependent = ModuleWrap::GetFromModule(env, referrer);
952993
if (dependent == nullptr) {
953994
THROW_ERR_VM_MODULE_LINK_FAILURE(
954995
env, "request for '%s' is from invalid module", cache_key.specifier);
955-
return MaybeLocal<Module>();
996+
return Nothing<ModuleWrap*>();
956997
}
957-
958-
if (dependent->resolve_cache_.count(cache_key) != 1) {
998+
if (!dependent->IsLinked()) {
959999
THROW_ERR_VM_MODULE_LINK_FAILURE(
960-
env, "request for '%s' is not in cache", cache_key.specifier);
961-
return MaybeLocal<Module>();
1000+
env,
1001+
"request for '%s' is from a module not been linked",
1002+
cache_key.specifier);
1003+
return Nothing<ModuleWrap*>();
9621004
}
9631005

964-
Local<Object> module_object =
965-
dependent->resolve_cache_[cache_key].Get(isolate);
966-
if (module_object.IsEmpty() || !module_object->IsObject()) {
1006+
auto it = dependent->resolve_cache_.find(cache_key);
1007+
if (it == dependent->resolve_cache_.end()) {
9671008
THROW_ERR_VM_MODULE_LINK_FAILURE(
968-
env, "request for '%s' did not return an object", cache_key.specifier);
969-
return MaybeLocal<Module>();
1009+
env, "request for '%s' is not in cache", cache_key.specifier);
1010+
return Nothing<ModuleWrap*>();
9701011
}
9711012

972-
ModuleWrap* module;
973-
ASSIGN_OR_RETURN_UNWRAP(&module, module_object, MaybeLocal<Module>());
974-
return module->module_.Get(isolate);
1013+
ModuleWrap* module_wrap = dependent->GetLinkedRequest(it->second);
1014+
CHECK_NOT_NULL(module_wrap);
1015+
return Just(module_wrap);
9751016
}
9761017

9771018
static MaybeLocal<Promise> ImportModuleDynamically(

src/module_wrap.h

Lines changed: 20 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -84,12 +84,16 @@ struct ModuleCacheKey : public MemoryRetainer {
8484
};
8585

8686
class ModuleWrap : public BaseObject {
87+
using ResolveCache =
88+
std::unordered_map<ModuleCacheKey, uint32_t, ModuleCacheKey::Hash>;
89+
8790
public:
8891
enum InternalFields {
8992
kModuleSlot = BaseObject::kInternalFieldCount,
9093
kURLSlot,
9194
kSyntheticEvaluationStepsSlot,
92-
kContextObjectSlot, // Object whose creation context is the target Context
95+
kContextObjectSlot, // Object whose creation context is the target Context
96+
kLinkedRequestsSlot, // Array of linked requests
9397
kInternalFieldCount
9498
};
9599

@@ -105,23 +109,25 @@ class ModuleWrap : public BaseObject {
105109
v8::Local<v8::Module> module,
106110
v8::Local<v8::Object> meta);
107111

108-
void MemoryInfo(MemoryTracker* tracker) const override {
109-
tracker->TrackField("resolve_cache", resolve_cache_);
110-
}
111112
static void HasTopLevelAwait(const v8::FunctionCallbackInfo<v8::Value>& args);
112113

113114
v8::Local<v8::Context> context() const;
114115
v8::Maybe<bool> CheckUnsettledTopLevelAwait();
115116

116117
SET_MEMORY_INFO_NAME(ModuleWrap)
117118
SET_SELF_SIZE(ModuleWrap)
119+
SET_NO_MEMORY_INFO()
118120

119121
bool IsNotIndicativeOfMemoryLeakAtExit() const override {
120122
// XXX: The garbage collection rules for ModuleWrap are *super* unclear.
121123
// Do these objects ever get GC'd? Are we just okay with leaking them?
122124
return true;
123125
}
124126

127+
bool IsLinked() const { return linked_; }
128+
129+
ModuleWrap* GetLinkedRequest(uint32_t index);
130+
125131
static v8::Local<v8::PrimitiveArray> GetHostDefinedOptions(
126132
v8::Isolate* isolate, v8::Local<v8::Symbol> symbol);
127133

@@ -183,13 +189,19 @@ class ModuleWrap : public BaseObject {
183189
v8::Local<v8::Module> referrer);
184190
static ModuleWrap* GetFromModule(node::Environment*, v8::Local<v8::Module>);
185191

192+
// This method may throw a JavaScript exception, so the return type is
193+
// wrapped in a Maybe.
194+
static v8::Maybe<ModuleWrap*> ResolveModule(
195+
v8::Local<v8::Context> context,
196+
v8::Local<v8::String> specifier,
197+
v8::Local<v8::FixedArray> import_attributes,
198+
v8::Local<v8::Module> referrer);
199+
186200
v8::Global<v8::Module> module_;
187-
std::unordered_map<ModuleCacheKey,
188-
v8::Global<v8::Object>,
189-
ModuleCacheKey::Hash>
190-
resolve_cache_;
201+
ResolveCache resolve_cache_;
191202
contextify::ContextifyContext* contextify_context_ = nullptr;
192203
bool synthetic_ = false;
204+
bool linked_ = false;
193205
int module_hash_;
194206
};
195207

test/parallel/test-internal-module-wrap.js

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,21 @@ const assert = require('assert');
66
const { internalBinding } = require('internal/test/binding');
77
const { ModuleWrap } = internalBinding('module_wrap');
88

9+
const unlinked = new ModuleWrap('unlinked', undefined, 'export * from "bar";', 0, 0);
10+
assert.throws(() => {
11+
unlinked.instantiate();
12+
}, {
13+
code: 'ERR_VM_MODULE_LINK_FAILURE',
14+
});
15+
16+
const dependsOnUnlinked = new ModuleWrap('dependsOnUnlinked', undefined, 'export * from "unlinked";', 0, 0);
17+
dependsOnUnlinked.link([unlinked]);
18+
assert.throws(() => {
19+
dependsOnUnlinked.instantiate();
20+
}, {
21+
code: 'ERR_VM_MODULE_LINK_FAILURE',
22+
});
23+
924
const foo = new ModuleWrap('foo', undefined, 'export * from "bar";', 0, 0);
1025
const bar = new ModuleWrap('bar', undefined, 'export const five = 5', 0, 0);
1126

@@ -22,4 +37,5 @@ const bar = new ModuleWrap('bar', undefined, 'export const five = 5', 0, 0);
2237

2338
// Check that the module requests are the same after linking, instantiate, and evaluation.
2439
assert.deepStrictEqual(moduleRequests, foo.getModuleRequests());
40+
2541
})().then(common.mustCall());

0 commit comments

Comments
 (0)