GeezerVisonThe World Wide Web, restored to 800 pixels of glory. | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| Home|About|Gaming cabinet|Archives|How it works|Starter sites | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
GeezerVison is showing github.com — esm: do not lazy instantiate loaders to avoid infinite loop by aduh95 · Pull Request #47599 · nodejs/node Conversation
Contributor
There are still failing tests to address. Fixes: #47566
Sorry, something went wrong. Uh oh!
There was an error while loading. Please reload this page. All reactions
Collaborator
All reactions
Sorry, something went wrong. Uh oh!
There was an error while loading. Please reload this page.
aduh95
force-pushed
the
esm-infinite-loop
branch
2 times, most recently
from
ce48cc0 to
efe037e
Compare
aduh95
marked this pull request as ready for review
aduh95
commented
Comment on lines
96
to
100
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Are we OK with this change? /cc @joyeecheung
Sorry, something went wrong. Uh oh!
There was an error while loading. Please reload this page. All reactions
|
| const customLoaderPaths = getOptionValue('--experimental-loader'); |
createModuleLoader() again if they access require('internal/process/esm_loader').esmLoader (e.g. for handling dynamic import) node/lib/internal/modules/esm/loader.js
Line 415 in 4667b07
| const userLoaderPaths = getOptionValue('--experimental-loader'); |
CustomizedModuleLoader which creates more workers.I think the proper synchronization should be done in createModuleLoader instead to make sure that when it's invoked within a internal loader worker - it can know that by just looking at some variable set by the worker main script, or the hooks proxy can use a different internal argument (instead of reusing --experimental-loader) to pass the loaders to the worker - it does not create that worker by constructing a CustomizedModuleLoader again. From what I can tell it probably should just return the default loader in the internal loader worker. I am not quite sure what this PR is currently doing but it looks like it's trying to achieve the same thing in a rather convoluted way - by introducing an intricate initialization order that somehow ends up leading to a mocked version of the loader in the esmLoader ??= statement when it's accessed within the internal worker. Which is...not very robust, and I doubt if the mock actually works for dynamic imports initiated in the custom loaders. Also from what I can tell I am not convinced this fix really needs eager-loading ESM loader, that looks like a by-product of the introduction of the intricate initialization order, which doesn't seem to be the right fix in the first place.
Sorry, something went wrong.
There was an error while loading. Please reload this page.
There was an error while loading. Please reload this page.
There was a problem hiding this comment.
The reason will be displayed to describe this comment to others. Learn more.
I have an alternate fix here: #47620 with a test that locally reproduces the bug for me. I am not sure if I am supposed to invest energy to push it forward soon (still recovering from COVID), I think I should keep myself away from the computer for the day :3 feel free to take over if I disappear from GitHub.
Sorry, something went wrong.
There was an error while loading. Please reload this page.
efe037e to
758fd93
Compare
Sorry, something went wrong.
There was an error while loading. Please reload this page.
| }, importRequests: [] }; | ||
| }, | ||
| initIfNeeded() { | ||
| // TODO: we could try to avoid loading ESM loader on CJS-only codebase |
There was an error while loading. Please reload this page.
There was a problem hiding this comment.
The reason will be displayed to describe this comment to others. Learn more.
I don't think we should initialize the ESM loader in CJS-only code base. At the very least the ESM internals must be snapshotable before we consider this (I suspect if we somehow make that happen, e.g. #45828, which requires resolving some of the the problematic circular dependencies, the bug here already might go away..)
Sorry, something went wrong.
There was an error while loading. Please reload this page.
There was an error while loading. Please reload this page.
There was a problem hiding this comment.
The reason will be displayed to describe this comment to others. Learn more.
We have to, because dynamic import() uses ESM loader. As I said in the TODO comment, we could instantiate the ESM loader only on the first dynamic import.
Sorry, something went wrong.
There was an error while loading. Please reload this page.
There was an error while loading. Please reload this page.
There was a problem hiding this comment.
The reason will be displayed to describe this comment to others. Learn more.
we could instantiate the ESM loader only on the first dynamic import.
We have already been doing it in the main branch:
node/lib/internal/modules/cjs/loader.js
Line 1196 in 2ec4ef7
| const cascadedLoader = getCascadedLoader(); |
Sorry, something went wrong.
There was an error while loading. Please reload this page.
There was a problem hiding this comment.
The reason will be displayed to describe this comment to others. Learn more.
At one point we had talking about handling all loading via ESMLoader; might that be the solution here?
Sorry, something went wrong.
There was an error while loading. Please reload this page.
| const { kEmptyObject, createDeferredPromise } = require('internal/util'); | ||
|
|
||
| let esmLoader; | ||
| let init = false; |
There was a problem hiding this comment.
The reason will be displayed to describe this comment to others. Learn more.
| let init = false; | |
| let initialized = false; |
Sorry, something went wrong.
There was an error while loading. Please reload this page.
| return module.exports.init(); | ||
| }, | ||
| init(loader = undefined) { | ||
| assert(!init); |
There was a problem hiding this comment.
The reason will be displayed to describe this comment to others. Learn more.
won't this throw? (I would think if (init) return)
Sorry, something went wrong.
There was an error while loading. Please reload this page.
| initIfNeeded() { | ||
| // TODO: we could try to avoid loading ESM loader on CJS-only codebase | ||
| return module.exports.init(); | ||
| }, | ||
| init(loader = undefined) { |
There was a problem hiding this comment.
The reason will be displayed to describe this comment to others. Learn more.
This is a bit convoluted, and not clear when which pieces should be called. Could it be baked into the esmLoader getter?
Sorry, something went wrong.
There was an error while loading. Please reload this page.
| module.exports = { | ||
| get esmLoader() { | ||
| return esmLoader ??= createModuleLoader(true); | ||
| return esmLoader ??= { __proto__: null, cjsCache: new SafeMap(), import() { |
There was a problem hiding this comment.
The reason will be displayed to describe this comment to others. Learn more.
cjsCache? What is this? Is this trying to track the callers that led to the creation of this ESM loader?
Sorry, something went wrong.
There was an error while loading. Please reload this page.
| assert.strictEqual(code, 0); | ||
| assert.strictEqual(signal, null); | ||
| }); | ||
|
|
There was a problem hiding this comment.
The reason will be displayed to describe this comment to others. Learn more.
Let’s add a test for the simplest case, ./node --loader ./test/fixtures/empty.js.
Sorry, something went wrong.
There was an error while loading. Please reload this page.
There was a problem hiding this comment.
The reason will be displayed to describe this comment to others. Learn more.
Tests that deal with REPL are simply not convenient and hard to not have flaky. Let's not do that?
Sorry, something went wrong.
There was an error while loading. Please reload this page.
There was a problem hiding this comment.
The reason will be displayed to describe this comment to others. Learn more.
Now that off-thread has landed I would be happy to combine some of the ESM files to make them snapshotable. I assume the goal is to add as much of the ESM loader as possible to the snapshot? I think it’s safe to say that ESM code is common enough at this point that most projects are likely to have at least some ESM code loaded between the main app and/or its dependencies.
Not that we should do this refactor in this PR, but I’m assuming it’s something we’d like to do?
Sorry, something went wrong.
There was an error while loading. Please reload this page.
|
Superseded by #47620. |
Sorry, something went wrong.
There was an error while loading. Please reload this page.
There was an error while loading. Please reload this page.
Successfully merging this pull request may close these issues.
Using custom loaders results in a load loop / OOM
There was an error while loading. Please reload this page.