Skip to content

Commit afd12e1

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, and caches the resolved indexes with a stack based variable `ModuleInstantiationContext`. 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`.
1 parent edd66d0 commit afd12e1

File tree

3 files changed

+261
-130
lines changed

3 files changed

+261
-130
lines changed

src/module_wrap.cc

Lines changed: 193 additions & 117 deletions
Original file line numberDiff line numberDiff line change
@@ -115,6 +115,140 @@ ModuleCacheKey ModuleCacheKey::From(Local<Context> context,
115115
context, v8_request->GetSpecifier(), v8_request->GetImportAttributes());
116116
}
117117

118+
// static
119+
thread_local ModuleInstantiationContext*
120+
ModuleInstantiationContext::thread_local_context_;
121+
122+
// static
123+
MaybeLocal<Module> ModuleInstantiationContext::ResolveModuleCallback(
124+
Local<Context> context,
125+
Local<String> specifier,
126+
Local<FixedArray> import_attributes,
127+
Local<Module> referrer) {
128+
CHECK_NOT_NULL(thread_local_context_);
129+
ModuleWrap* resolved_module;
130+
if (!thread_local_context_
131+
->ResolveModule(context, specifier, import_attributes, referrer)
132+
.To(&resolved_module)) {
133+
return {};
134+
}
135+
DCHECK_NOT_NULL(resolved_module);
136+
return resolved_module->module();
137+
}
138+
139+
// static
140+
MaybeLocal<Object> ModuleInstantiationContext::ResolveSourceCallback(
141+
Local<Context> context,
142+
Local<String> specifier,
143+
Local<FixedArray> import_attributes,
144+
Local<Module> referrer) {
145+
CHECK_NOT_NULL(thread_local_context_);
146+
ModuleWrap* resolved_module;
147+
if (!thread_local_context_
148+
->ResolveModule(context, specifier, import_attributes, referrer)
149+
.To(&resolved_module)) {
150+
return {};
151+
}
152+
DCHECK_NOT_NULL(resolved_module);
153+
154+
Local<Value> module_source_object =
155+
resolved_module->object()
156+
->GetInternalField(ModuleWrap::kModuleSourceObjectSlot)
157+
.As<Value>();
158+
if (module_source_object->IsUndefined()) {
159+
Local<String> url = resolved_module->object()
160+
->GetInternalField(ModuleWrap::kURLSlot)
161+
.As<String>();
162+
THROW_ERR_SOURCE_PHASE_NOT_DEFINED(context->GetIsolate(), url);
163+
return {};
164+
}
165+
CHECK(module_source_object->IsObject());
166+
return module_source_object.As<Object>();
167+
}
168+
169+
ModuleInstantiationContext::ModuleInstantiationContext() {
170+
// Only one ModuleInstantiationContext can exist per thread at a time.
171+
CHECK_NULL(thread_local_context_);
172+
thread_local_context_ = this;
173+
}
174+
175+
ModuleInstantiationContext::~ModuleInstantiationContext() {
176+
// Ensure that the thread-local context is this context.
177+
CHECK_EQ(thread_local_context_, this);
178+
thread_local_context_ = nullptr;
179+
}
180+
181+
Maybe<ModuleWrap*> ModuleInstantiationContext::ResolveModule(
182+
Local<Context> context,
183+
Local<String> specifier,
184+
Local<FixedArray> import_attributes,
185+
Local<Module> referrer) {
186+
Isolate* isolate = context->GetIsolate();
187+
Environment* env = Environment::GetCurrent(context);
188+
if (env == nullptr) {
189+
THROW_ERR_EXECUTION_ENVIRONMENT_NOT_AVAILABLE(isolate);
190+
return Nothing<ModuleWrap*>();
191+
}
192+
// Check that the referrer is not yet been instantiated.
193+
DCHECK(referrer->GetStatus() <= Module::kInstantiated);
194+
195+
ModuleCacheKey cache_key =
196+
ModuleCacheKey::From(context, specifier, import_attributes);
197+
198+
ModuleWrap* dependent = ModuleWrap::GetFromModule(env, referrer);
199+
if (dependent == nullptr) {
200+
THROW_ERR_VM_MODULE_LINK_FAILURE(
201+
env, "request for '%s' is from invalid module", cache_key.specifier);
202+
return Nothing<ModuleWrap*>();
203+
}
204+
if (!dependent->IsLinked()) {
205+
THROW_ERR_VM_MODULE_LINK_FAILURE(
206+
env,
207+
"request for '%s' is from a module not been linked",
208+
cache_key.specifier);
209+
return Nothing<ModuleWrap*>();
210+
}
211+
212+
ResolveCache& resolve_cache = GetModuleResolveCache(env, dependent);
213+
if (resolve_cache.count(cache_key) != 1) {
214+
THROW_ERR_VM_MODULE_LINK_FAILURE(
215+
env, "request for '%s' is not in cache", cache_key.specifier);
216+
return Nothing<ModuleWrap*>();
217+
}
218+
219+
ModuleWrap* module_wrap =
220+
dependent->GetLinkedRequest(resolve_cache[cache_key]);
221+
CHECK_NOT_NULL(module_wrap);
222+
return Just(module_wrap);
223+
}
224+
225+
ModuleInstantiationContext::ResolveCache&
226+
ModuleInstantiationContext::GetModuleResolveCache(Environment* env,
227+
ModuleWrap* module_wrap) {
228+
CHECK(module_wrap->IsLinked());
229+
230+
auto it = module_instance_graph_.find(module_wrap);
231+
if (it != module_instance_graph_.end()) {
232+
return it->second;
233+
}
234+
235+
Isolate* isolate = env->isolate();
236+
HandleScope scope(isolate);
237+
Local<Context> context = env->context();
238+
239+
Local<FixedArray> requests = module_wrap->module()->GetModuleRequests();
240+
241+
ResolveCache& resolve_cache = module_instance_graph_[module_wrap];
242+
243+
for (int i = 0; i < requests->Length(); i++) {
244+
ModuleCacheKey module_cache_key = ModuleCacheKey::From(
245+
context, requests->Get(context, i).As<ModuleRequest>());
246+
resolve_cache[module_cache_key] = i;
247+
}
248+
249+
return resolve_cache;
250+
}
251+
118252
ModuleWrap::ModuleWrap(Realm* realm,
119253
Local<Object> object,
120254
Local<Module> module,
@@ -133,6 +267,8 @@ ModuleWrap::ModuleWrap(Realm* realm,
133267
object->SetInternalField(kSyntheticEvaluationStepsSlot,
134268
synthetic_evaluation_step);
135269
object->SetInternalField(kContextObjectSlot, context_object);
270+
object->SetInternalField(kLinkedRequestsSlot,
271+
v8::Undefined(realm->isolate()));
136272

137273
if (!synthetic_evaluation_step->IsUndefined()) {
138274
synthetic_ = true;
@@ -159,6 +295,34 @@ Local<Context> ModuleWrap::context() const {
159295
return obj.As<Object>()->GetCreationContextChecked();
160296
}
161297

298+
Local<Module> ModuleWrap::module() {
299+
return module_.Get(env()->isolate());
300+
}
301+
302+
ModuleWrap* ModuleWrap::GetLinkedRequest(uint32_t index) {
303+
DCHECK(IsLinked());
304+
Isolate* isolate = env()->isolate();
305+
EscapableHandleScope scope(isolate);
306+
Local<Data> linked_requests_data =
307+
object()->GetInternalField(kLinkedRequestsSlot);
308+
DCHECK(linked_requests_data->IsValue() &&
309+
linked_requests_data.As<Value>()->IsArray());
310+
Local<Array> requests = linked_requests_data.As<Array>();
311+
312+
CHECK_LT(index, requests->Length());
313+
314+
Local<Value> module_value;
315+
if (!requests->Get(context(), index).ToLocal(&module_value)) {
316+
return nullptr;
317+
}
318+
CHECK(module_value->IsObject());
319+
Local<Object> module_object = module_value.As<Object>();
320+
321+
ModuleWrap* module_wrap;
322+
ASSIGN_OR_RETURN_UNWRAP(&module_wrap, module_object, nullptr);
323+
return module_wrap;
324+
}
325+
162326
ModuleWrap* ModuleWrap::GetFromModule(Environment* env,
163327
Local<Module> module) {
164328
auto range = env->hash_to_module_map.equal_range(module->GetIdentityHash());
@@ -571,34 +735,28 @@ void ModuleWrap::GetModuleRequests(const FunctionCallbackInfo<Value>& args) {
571735
void ModuleWrap::Link(const FunctionCallbackInfo<Value>& args) {
572736
Realm* realm = Realm::GetCurrent(args);
573737
Isolate* isolate = args.GetIsolate();
574-
Local<Context> context = realm->context();
575738

576739
ModuleWrap* dependent;
577740
ASSIGN_OR_RETURN_UNWRAP(&dependent, args.This());
578741

579742
CHECK_EQ(args.Length(), 1);
580743

744+
Local<Data> linked_requests =
745+
args.This()->GetInternalField(kLinkedRequestsSlot);
746+
if (linked_requests->IsValue() &&
747+
!linked_requests.As<Value>()->IsUndefined()) {
748+
// If the module is already linked, we should not link it again.
749+
THROW_ERR_VM_MODULE_LINK_FAILURE(realm->env(), "module is already linked");
750+
return;
751+
}
752+
581753
Local<FixedArray> requests =
582754
dependent->module_.Get(isolate)->GetModuleRequests();
583755
Local<Array> modules = args[0].As<Array>();
584756
CHECK_EQ(modules->Length(), static_cast<uint32_t>(requests->Length()));
585757

586-
std::vector<Global<Value>> modules_buffer;
587-
if (FromV8Array(context, modules, &modules_buffer).IsNothing()) {
588-
return;
589-
}
590-
591-
for (uint32_t i = 0; i < modules_buffer.size(); i++) {
592-
Local<Object> module_object = modules_buffer[i].Get(isolate).As<Object>();
593-
594-
CHECK(
595-
realm->isolate_data()->module_wrap_constructor_template()->HasInstance(
596-
module_object));
597-
598-
ModuleCacheKey module_cache_key = ModuleCacheKey::From(
599-
context, requests->Get(context, i).As<ModuleRequest>());
600-
dependent->resolve_cache_[module_cache_key].Reset(isolate, module_object);
601-
}
758+
args.This()->SetInternalField(kLinkedRequestsSlot, modules);
759+
dependent->linked_ = true;
602760
}
603761

604762
void ModuleWrap::Instantiate(const FunctionCallbackInfo<Value>& args) {
@@ -609,11 +767,16 @@ void ModuleWrap::Instantiate(const FunctionCallbackInfo<Value>& args) {
609767
Local<Context> context = obj->context();
610768
Local<Module> module = obj->module_.Get(isolate);
611769
TryCatchScope try_catch(realm->env());
612-
USE(module->InstantiateModule(
613-
context, ResolveModuleCallback, ResolveSourceCallback));
614770

615-
// clear resolve cache on instantiate
616-
obj->resolve_cache_.clear();
771+
{
772+
ModuleInstantiationContext instantiation_context;
773+
USE(module->InstantiateModule(
774+
context,
775+
ModuleInstantiationContext::ResolveModuleCallback,
776+
ModuleInstantiationContext::ResolveSourceCallback));
777+
778+
// instantiation_context goes out of scope.
779+
}
617780

618781
if (try_catch.HasCaught() && !try_catch.HasTerminated()) {
619782
CHECK(!try_catch.Message().IsEmpty());
@@ -719,11 +882,16 @@ void ModuleWrap::InstantiateSync(const FunctionCallbackInfo<Value>& args) {
719882

720883
{
721884
TryCatchScope try_catch(env);
722-
USE(module->InstantiateModule(
723-
context, ResolveModuleCallback, ResolveSourceCallback));
724885

725-
// clear resolve cache on instantiate
726-
obj->resolve_cache_.clear();
886+
{
887+
ModuleInstantiationContext instantiation_context;
888+
USE(module->InstantiateModule(
889+
context,
890+
ModuleInstantiationContext::ResolveModuleCallback,
891+
ModuleInstantiationContext::ResolveSourceCallback));
892+
893+
// instantiation_context goes out of scope.
894+
}
727895

728896
if (try_catch.HasCaught() && !try_catch.HasTerminated()) {
729897
CHECK(!try_catch.Message().IsEmpty());
@@ -965,98 +1133,6 @@ void ModuleWrap::GetError(const FunctionCallbackInfo<Value>& args) {
9651133
args.GetReturnValue().Set(module->GetException());
9661134
}
9671135

968-
MaybeLocal<Module> ModuleWrap::ResolveModuleCallback(
969-
Local<Context> context,
970-
Local<String> specifier,
971-
Local<FixedArray> import_attributes,
972-
Local<Module> referrer) {
973-
Isolate* isolate = context->GetIsolate();
974-
Environment* env = Environment::GetCurrent(context);
975-
if (env == nullptr) {
976-
THROW_ERR_EXECUTION_ENVIRONMENT_NOT_AVAILABLE(isolate);
977-
return MaybeLocal<Module>();
978-
}
979-
980-
ModuleCacheKey cache_key =
981-
ModuleCacheKey::From(context, specifier, import_attributes);
982-
983-
ModuleWrap* dependent = GetFromModule(env, referrer);
984-
if (dependent == nullptr) {
985-
THROW_ERR_VM_MODULE_LINK_FAILURE(
986-
env, "request for '%s' is from invalid module", cache_key.specifier);
987-
return MaybeLocal<Module>();
988-
}
989-
990-
if (dependent->resolve_cache_.count(cache_key) != 1) {
991-
THROW_ERR_VM_MODULE_LINK_FAILURE(
992-
env, "request for '%s' is not in cache", cache_key.specifier);
993-
return MaybeLocal<Module>();
994-
}
995-
996-
Local<Object> module_object =
997-
dependent->resolve_cache_[cache_key].Get(isolate);
998-
if (module_object.IsEmpty() || !module_object->IsObject()) {
999-
THROW_ERR_VM_MODULE_LINK_FAILURE(
1000-
env, "request for '%s' did not return an object", cache_key.specifier);
1001-
return MaybeLocal<Module>();
1002-
}
1003-
1004-
ModuleWrap* module;
1005-
ASSIGN_OR_RETURN_UNWRAP(&module, module_object, MaybeLocal<Module>());
1006-
return module->module_.Get(isolate);
1007-
}
1008-
1009-
MaybeLocal<Object> ModuleWrap::ResolveSourceCallback(
1010-
Local<Context> context,
1011-
Local<String> specifier,
1012-
Local<FixedArray> import_attributes,
1013-
Local<Module> referrer) {
1014-
Isolate* isolate = context->GetIsolate();
1015-
Environment* env = Environment::GetCurrent(context);
1016-
if (env == nullptr) {
1017-
THROW_ERR_EXECUTION_ENVIRONMENT_NOT_AVAILABLE(isolate);
1018-
return MaybeLocal<Object>();
1019-
}
1020-
1021-
ModuleCacheKey cache_key =
1022-
ModuleCacheKey::From(context, specifier, import_attributes);
1023-
1024-
ModuleWrap* dependent = GetFromModule(env, referrer);
1025-
if (dependent == nullptr) {
1026-
THROW_ERR_VM_MODULE_LINK_FAILURE(
1027-
env, "request for '%s' is from invalid module", cache_key.specifier);
1028-
return MaybeLocal<Object>();
1029-
}
1030-
1031-
if (dependent->resolve_cache_.count(cache_key) != 1) {
1032-
THROW_ERR_VM_MODULE_LINK_FAILURE(
1033-
env, "request for '%s' is not in cache", cache_key.specifier);
1034-
return MaybeLocal<Object>();
1035-
}
1036-
1037-
Local<Object> module_object =
1038-
dependent->resolve_cache_[cache_key].Get(isolate);
1039-
if (module_object.IsEmpty() || !module_object->IsObject()) {
1040-
THROW_ERR_VM_MODULE_LINK_FAILURE(
1041-
env, "request for '%s' did not return an object", cache_key.specifier);
1042-
return MaybeLocal<Object>();
1043-
}
1044-
1045-
ModuleWrap* module;
1046-
ASSIGN_OR_RETURN_UNWRAP(&module, module_object, MaybeLocal<Object>());
1047-
1048-
Local<Value> module_source_object =
1049-
module->object()->GetInternalField(kModuleSourceObjectSlot).As<Value>();
1050-
if (module_source_object->IsUndefined()) {
1051-
Local<String> url =
1052-
module->object()->GetInternalField(kURLSlot).As<String>();
1053-
THROW_ERR_SOURCE_PHASE_NOT_DEFINED(isolate, url);
1054-
return MaybeLocal<Object>();
1055-
}
1056-
CHECK(module_source_object->IsObject());
1057-
return module_source_object.As<Object>();
1058-
}
1059-
10601136
static MaybeLocal<Promise> ImportModuleDynamicallyWithPhase(
10611137
Local<Context> context,
10621138
Local<Data> host_defined_options,

0 commit comments

Comments
 (0)