Skip to content

Commit b201582

Browse files
committed
lib,src: iterate module requests of a module wrap in JS
Avoid repetitively calling into JS callback from C++ in `ModuleWrap::Link`. This removes the convoluted callback style of the internal `ModuleWrap` link step.
1 parent 63d04d4 commit b201582

File tree

6 files changed

+163
-162
lines changed

6 files changed

+163
-162
lines changed

lib/internal/modules/esm/module_job.js

+33-23
Original file line numberDiff line numberDiff line change
@@ -78,30 +78,8 @@ class ModuleJob {
7878
this.modulePromise = PromiseResolve(this.modulePromise);
7979
}
8080

81-
// Wait for the ModuleWrap instance being linked with all dependencies.
82-
const link = async () => {
83-
this.module = await this.modulePromise;
84-
assert(this.module instanceof ModuleWrap);
85-
86-
// Explicitly keeping track of dependency jobs is needed in order
87-
// to flatten out the dependency graph below in `_instantiate()`,
88-
// so that circular dependencies can't cause a deadlock by two of
89-
// these `link` callbacks depending on each other.
90-
const dependencyJobs = [];
91-
const promises = this.module.link(async (specifier, attributes) => {
92-
const job = await this.loader.getModuleJob(specifier, url, attributes);
93-
ArrayPrototypePush(dependencyJobs, job);
94-
return job.modulePromise;
95-
});
96-
97-
if (promises !== undefined) {
98-
await SafePromiseAllReturnVoid(promises);
99-
}
100-
101-
return SafePromiseAllReturnArrayLike(dependencyJobs);
102-
};
10381
// Promise for the list of all dependencyJobs.
104-
this.linked = link();
82+
this.linked = this._link();
10583
// This promise is awaited later anyway, so silence
10684
// 'unhandled rejection' warnings.
10785
PromisePrototypeThen(this.linked, undefined, noop);
@@ -111,6 +89,38 @@ class ModuleJob {
11189
this.instantiated = undefined;
11290
}
11391

92+
/**
93+
* Iterates the module requests and links with the loader.
94+
* @returns {Promise<ModuleJob[]>} Dependency module jobs.
95+
*/
96+
async _link() {
97+
this.module = await this.modulePromise;
98+
assert(this.module instanceof ModuleWrap);
99+
100+
// Explicitly keeping track of dependency jobs is needed in order
101+
// to flatten out the dependency graph below in `_instantiate()`,
102+
// so that circular dependencies can't cause a deadlock by two of
103+
// these `link` callbacks depending on each other.
104+
const dependencyJobs = [];
105+
const promises = [];
106+
// Iterate with index to avoid calling into userspace with `Symbol.iterator`.
107+
const moduleRequestsLength = this.module.moduleRequests.length;
108+
for (let idx = 0; idx < moduleRequestsLength; idx++) {
109+
const { specifier, attributes } = this.module.moduleRequests[idx];
110+
const job = await this.loader.getModuleJob(specifier, this.url, attributes);
111+
ArrayPrototypePush(dependencyJobs, job);
112+
ArrayPrototypePush(promises, job.modulePromise);
113+
this.module.linkModule(
114+
specifier,
115+
job.modulePromise,
116+
/** finished */ idx === moduleRequestsLength - 1,
117+
);
118+
}
119+
120+
await SafePromiseAllReturnVoid(promises);
121+
return SafePromiseAllReturnArrayLike(dependencyJobs);
122+
}
123+
114124
instantiate() {
115125
if (this.instantiated === undefined) {
116126
this.instantiated = this._instantiate();

lib/internal/vm/module.js

+39-23
Original file line numberDiff line numberDiff line change
@@ -5,11 +5,15 @@ const {
55
ArrayIsArray,
66
ArrayPrototypeForEach,
77
ArrayPrototypeIndexOf,
8+
ArrayPrototypeMap,
9+
ArrayPrototypePush,
810
ArrayPrototypeSome,
911
ObjectDefineProperty,
1012
ObjectFreeze,
1113
ObjectGetPrototypeOf,
1214
ObjectSetPrototypeOf,
15+
PromiseResolve,
16+
PromisePrototypeThen,
1317
ReflectApply,
1418
SafePromiseAllReturnVoid,
1519
Symbol,
@@ -306,27 +310,40 @@ class SourceTextModule extends Module {
306310
this[kLink] = async (linker) => {
307311
this.#statusOverride = 'linking';
308312

309-
const promises = this[kWrap].link(async (identifier, attributes) => {
310-
const module = await linker(identifier, this, { attributes, assert: attributes });
311-
if (module[kWrap] === undefined) {
312-
throw new ERR_VM_MODULE_NOT_MODULE();
313-
}
314-
if (module.context !== this.context) {
315-
throw new ERR_VM_MODULE_DIFFERENT_CONTEXT();
316-
}
317-
if (module.status === 'errored') {
318-
throw new ERR_VM_MODULE_LINK_FAILURE(`request for '${identifier}' resolved to an errored module`, module.error);
319-
}
320-
if (module.status === 'unlinked') {
321-
await module[kLink](linker);
322-
}
323-
return module[kWrap];
324-
});
313+
// Iterates the module requests and links with the linker.
314+
const promises = [];
315+
// Iterate with index to avoid calling into userspace with `Symbol.iterator`.
316+
const moduleRequestsLength = this[kWrap].moduleRequests.length;
317+
for (let idx = 0; idx < moduleRequestsLength; idx++) {
318+
const { specifier, attributes } = this[kWrap].moduleRequests[idx];
319+
320+
const linkerResult = linker(specifier, this, {
321+
attributes,
322+
assert: attributes,
323+
});
324+
const promise = PromisePrototypeThen(
325+
PromiseResolve(linkerResult), async (module) => {
326+
if (module[kWrap] === undefined) {
327+
throw new ERR_VM_MODULE_NOT_MODULE();
328+
}
329+
if (module.context !== this.context) {
330+
throw new ERR_VM_MODULE_DIFFERENT_CONTEXT();
331+
}
332+
if (module.status === 'errored') {
333+
throw new ERR_VM_MODULE_LINK_FAILURE(`request for '${specifier}' resolved to an errored module`, module.error);
334+
}
335+
if (module.status === 'unlinked') {
336+
await module[kLink](linker);
337+
}
338+
return module[kWrap];
339+
});
340+
341+
this[kWrap].linkModule(specifier, promise, /** finished */ idx === moduleRequestsLength - 1);
342+
ArrayPrototypePush(promises, promise);
343+
}
325344

326345
try {
327-
if (promises !== undefined) {
328-
await SafePromiseAllReturnVoid(promises);
329-
}
346+
await SafePromiseAllReturnVoid(promises);
330347
} catch (e) {
331348
this.#error = e;
332349
throw e;
@@ -342,7 +359,8 @@ class SourceTextModule extends Module {
342359
if (this[kWrap] === undefined) {
343360
throw new ERR_VM_MODULE_NOT_MODULE();
344361
}
345-
this[kDependencySpecifiers] ??= ObjectFreeze(this[kWrap].getStaticDependencySpecifiers());
362+
this[kDependencySpecifiers] ??= ObjectFreeze(
363+
ArrayPrototypeMap(this[kWrap].moduleRequests, (request) => request.specifier));
346364
return this[kDependencySpecifiers];
347365
}
348366

@@ -409,9 +427,7 @@ class SyntheticModule extends Module {
409427
identifier,
410428
});
411429

412-
this[kLink] = () => this[kWrap].link(() => {
413-
assert.fail('link callback should not be called');
414-
});
430+
this[kLink] = () => this[kWrap].markLinked();
415431
}
416432

417433
setExport(name, value) {

src/env_properties.h

+3
Original file line numberDiff line numberDiff line change
@@ -67,6 +67,7 @@
6767
V(args_string, "args") \
6868
V(asn1curve_string, "asn1Curve") \
6969
V(async_ids_stack_string, "async_ids_stack") \
70+
V(attributes_string, "attributes") \
7071
V(base_string, "base") \
7172
V(bits_string, "bits") \
7273
V(block_list_string, "blockList") \
@@ -213,6 +214,7 @@
213214
V(mgf1_hash_algorithm_string, "mgf1HashAlgorithm") \
214215
V(minttl_string, "minttl") \
215216
V(module_string, "module") \
217+
V(module_requests_string, "moduleRequests") \
216218
V(modulus_string, "modulus") \
217219
V(modulus_length_string, "modulusLength") \
218220
V(name_string, "name") \
@@ -300,6 +302,7 @@
300302
V(sni_context_string, "sni_context") \
301303
V(source_string, "source") \
302304
V(source_map_url_string, "sourceMapURL") \
305+
V(specifier_string, "specifier") \
303306
V(stack_string, "stack") \
304307
V(standard_name_string, "standardName") \
305308
V(start_time_string, "startTime") \

0 commit comments

Comments
 (0)