Skip to content
Closed
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Prev Previous commit
Next Next commit
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.
  • Loading branch information
joyeecheung committed May 7, 2024
commit 0f4b1200cbc789e8e5081b5d9c62056740afa6e7
39 changes: 25 additions & 14 deletions lib/internal/modules/esm/module_job.js
Original file line number Diff line number Diff line change
Expand Up @@ -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);
Comment thread
anonrig marked this conversation as resolved.
// Store itself into the cache first before linking in case there are circular
// references in the linking.
loader.loadCache.set(url, importAttributes.type, this);
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Setting in cache at

job = new ModuleJobSync(this, url, importAttributes, wrap, isMain, inspectBrk);
this.loadCache.set(url, importAttributes.type, job);
would be redundant but I think it could be a follow up work.

Copy link
Copy Markdown
Member Author

@joyeecheung joyeecheung May 8, 2024

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

That was what I did initially but it would cache modules that failed to link and if that’s caught, subsequent attempts would not fail as expected - which is why I added comments explaining why it’s reset in the finally block (doing it inside the linking phase prevents a rethrow in the caller which looks less nice)

Copy link
Copy Markdown
Member

@legendecas legendecas May 8, 2024

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Hmmm, maybe we could transform the try-finally into a try-catch-rethrow and only delete in catch? In this way we don't need to set cache again in the caller.

Copy link
Copy Markdown
Member Author

@joyeecheung joyeecheung May 10, 2024

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The rethrow was exactly what I am trying to prevent - setting the cache again is fine as the overhead is trivial but having to rethrow in all callers of this makes the code harder to read IMO. It also makes all the source lines of the errors less readable, which shows in the stderr when they are not caught.


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() {
Expand Down
6 changes: 6 additions & 0 deletions lib/internal/modules/esm/module_map.js
Original file line number Diff line number Diff line change
Expand Up @@ -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 = {
Expand Down
8 changes: 8 additions & 0 deletions test/es-module/test-require-module-cycle-cjs-esm-esm.js
Original file line number Diff line number Diff line change
@@ -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 });
2 changes: 2 additions & 0 deletions test/fixtures/es-modules/cjs-esm-esm-cycle/a.mjs
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
export { b } from './b.mjs';

Comment thread
joyeecheung marked this conversation as resolved.
Outdated
2 changes: 2 additions & 0 deletions test/fixtures/es-modules/cjs-esm-esm-cycle/b.mjs
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
import './a.mjs'
export const b = 5;
1 change: 1 addition & 0 deletions test/fixtures/es-modules/cjs-esm-esm-cycle/c.cjs
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
module.exports = require('./a.mjs');