module: cache synchronous module jobs before linking

So that if there are circular dependencies in the synchronous
module graph, they could be resolved using the cached jobs.
In case linking fails and the error gets caught, reset the
cache right after linking. If it succeeds, the caller will
cache it again. Otherwise the error bubbles up to users,
and since we unset the cache for the unlinkable module
the next attempt would still fail.

PR-URL: https://github.com/nodejs/node/pull/52868
Fixes: https://github.com/nodejs/node/issues/52864
Reviewed-By: Moshe Atlow <moshe@atlow.co.il>
Reviewed-By: Vinícius Lourenço Claro Cardoso <contact@viniciusl.com.br>
Reviewed-By: Antoine du Hamel <duhamelantoine1995@gmail.com>
Reviewed-By: Chengzhong Wu <legendecas@gmail.com>
This commit is contained in:
Joyee Cheung 2024-05-07 08:12:16 +08:00 committed by Node.js GitHub Bot
parent 7443cbc33b
commit 2863c54257
6 changed files with 43 additions and 14 deletions

View file

@ -295,22 +295,33 @@ class ModuleJobSync extends ModuleJobBase {
#loader = null;
constructor(loader, url, importAttributes, moduleWrap, isMain, inspectBrk) {
super(url, importAttributes, moduleWrap, isMain, inspectBrk, true);
assert(this.module instanceof ModuleWrap);
this.#loader = loader;
const moduleRequests = this.module.getModuleRequests();
// Specifiers should be aligned with the moduleRequests array in order.
const specifiers = Array(moduleRequests.length);
const modules = Array(moduleRequests.length);
const jobs = Array(moduleRequests.length);
for (let i = 0; i < moduleRequests.length; ++i) {
const { specifier, attributes } = moduleRequests[i];
const job = this.#loader.getModuleJobForRequire(specifier, url, attributes);
specifiers[i] = specifier;
modules[i] = job.module;
jobs[i] = job;
assert(this.module instanceof ModuleWrap);
// Store itself into the cache first before linking in case there are circular
// references in the linking.
loader.loadCache.set(url, importAttributes.type, this);
try {
const moduleRequests = this.module.getModuleRequests();
// Specifiers should be aligned with the moduleRequests array in order.
const specifiers = Array(moduleRequests.length);
const modules = Array(moduleRequests.length);
const jobs = Array(moduleRequests.length);
for (let i = 0; i < moduleRequests.length; ++i) {
const { specifier, attributes } = moduleRequests[i];
const job = this.#loader.getModuleJobForRequire(specifier, url, attributes);
specifiers[i] = specifier;
modules[i] = job.module;
jobs[i] = job;
}
this.module.link(specifiers, modules);
this.linked = jobs;
} finally {
// Restore it - if it succeeds, we'll reset in the caller; Otherwise it's
// not cached and if the error is caught, subsequent attempt would still fail.
loader.loadCache.delete(url, importAttributes.type);
}
this.module.link(specifiers, modules);
this.linked = jobs;
}
get modulePromise() {

View file

@ -114,6 +114,12 @@ class LoadCache extends SafeMap {
validateString(type, 'type');
return super.get(url)?.[type] !== undefined;
}
delete(url, type = kImplicitAssertType) {
const cached = super.get(url);
if (cached) {
cached[type] = undefined;
}
}
}
module.exports = {

View file

@ -0,0 +1,8 @@
// Flags: --experimental-require-module
'use strict';
// This tests that ESM <-> ESM cycle is allowed in a require()-d graph.
const common = require('../common');
const cycle = require('../fixtures/es-modules/cjs-esm-esm-cycle/c.cjs');
common.expectRequiredModule(cycle, { b: 5 });

View file

@ -0,0 +1 @@
export { b } from './b.mjs';

View file

@ -0,0 +1,2 @@
import './a.mjs'
export const b = 5;

View file

@ -0,0 +1 @@
module.exports = require('./a.mjs');