Skip to content

Conversation

@joyeecheung
Copy link
Member

@joyeecheungjoyeecheung commented Feb 23, 2025

This handles two cases caused by using Promise.all() with multiple dynamic import() that can make an asynchronously linked module job that has finished/failed linking but has not yet started actual evaluation appear in the load cache when another require request is in turn to handle it. It's similar to how we handle the case when the two are both asynchronous.

  • When the cached async job has finished linking but has not started its evaluation because the async run() task would be later in line, start the evaluation from require() with runSync(). The evaluation, being synchronous, is guaranteed to complete by the time the other run() is about to start evaluation, the other task would just get the cached evaluation results instead of re-evaluating it, similar to what would happen if both are asynchronously imported.
  • When the cached async job have already encountered a linking error that gets wrapped into a rejection, but is still later in line to throw on it, just unwrap and throw the linking error from require().

Refs: shufo/prettier-plugin-blade#311
Refs: https://github.com/fisker/prettier-plugin-blade-311
Refs: mochajs/mocha#5290
Refs: https://github.com/JoshuaKGoldberg/repros/tree/mocha-missing-module-cyclic
Fixes: #57172

@nodejs-github-bot
Copy link
Collaborator

Review requested:

  • @nodejs/loaders
  • @nodejs/vm

@nodejs-github-botnodejs-github-bot added c++ Issues and PRs that require attention from people who are familiar with C++. esm Issues and PRs related to the ECMAScript Modules implementation. needs-ci PRs that need a full CI run. labels Feb 23, 2025
@joyeecheung
Copy link
MemberAuthor

joyeecheung commented Feb 23, 2025

I think it's still possible to have cached async jobs yielding JS-land handling while another require(esm) comes in and pick up the cache for the same module. In those cases, we can't really handle them as we can't turn a linking process that has started asynchronously to switch to a synchronous route. To get rid of these edge cases once and for all, we should do at least part of #55782 and make the linking fully synchronous. But I haven't seen those edge cases so far and the refactoring would be more complex to complete. Until then, this fixes the two cases that we can handle without refactoring and have appeared in the user land.

I don't think I can minimally reproduce the shape of the microtask queue of the two cases, as they depend on how linking and evaluation tasks from a complex graph happen to be interleaved. So I didn't add any tests, but I can confirm locally it fixes https://github.com/fisker/prettier-plugin-blade-311 and https://github.com/JoshuaKGoldberg/repros/tree/mocha-missing-module-cyclic

@codecov
Copy link

codecovbot commented Feb 24, 2025

Codecov Report

❌ Patch coverage is 69.38776% with 30 lines in your changes missing coverage. Please review.
✅ Project coverage is 90.23%. Comparing base (47a59bd) to head (be776fa).
⚠️ Report is 1150 commits behind head on main.

Files with missing linesPatch %Lines
lib/internal/modules/esm/loader.js62.68%24 Missing and 1 partial ⚠️
src/module_wrap.cc84.61%2 Missing and 2 partials ⚠️
lib/internal/modules/esm/module_job.js80.00%1 Missing ⚠️
Additional details and impacted files
@@ Coverage Diff @@## main #57187 +/- ## ========================================== - Coverage 90.24% 90.23% -0.01%  ========================================== Files 630 630 Lines 184920 184980 +60 Branches 36186 36198 +12 ========================================== + Hits 166874 166920 +46 - Misses 11059 11088 +29 + Partials 6987 6972 -15 
Files with missing linesCoverage Δ
lib/internal/modules/esm/module_job.js98.27% <80.00%> (-0.22%)⬇️
src/module_wrap.cc72.45% <84.61%> (+0.22%)⬆️
lib/internal/modules/esm/loader.js95.04% <62.68%> (-1.64%)⬇️

... and 34 files with indirect coverage changes

🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.
  • 📦 JS Bundle Analysis: Save yourself from yourself by tracking and limiting bundle sizes in JS merges.

@joyeecheungjoyeecheung added the request-ci Add this label to start a Jenkins CI on a PR. label Feb 24, 2025
@github-actionsgithub-actionsbot removed the request-ci Add this label to start a Jenkins CI on a PR. label Feb 24, 2025
@nodejs-github-bot
Copy link
Collaborator

Copy link
Contributor

@guybedfordguybedford left a comment

Choose a reason for hiding this comment

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

Would be great to see this backported.

@joyeecheungjoyeecheung added the request-ci Add this label to start a Jenkins CI on a PR. label Mar 3, 2025
This handles two cases caused by using Promise.all() with multiple dynamic import() that can make an asynchronously linked module job that has finished/failed linking but has not yet started actual evaluation appear in the load cache when another require request is in turn to handle it. - When the cached async job has finished linking but has not started its evaluation because the async run() task would be later in line, start the evaluation from require() with runSync(). - When the cached async job have already encountered a linking error that gets wrapped into a rejection, but is still later in line to throw on it, just unwrap and throw the linking error from require().
@github-actionsgithub-actionsbot removed the request-ci Add this label to start a Jenkins CI on a PR. label Mar 3, 2025
@nodejs-github-bot
Copy link
Collaborator

@joyeecheungjoyeecheung added the request-ci Add this label to start a Jenkins CI on a PR. label Mar 3, 2025
@github-actionsgithub-actionsbot removed the request-ci Add this label to start a Jenkins CI on a PR. label Mar 3, 2025
@nodejs-github-bot
Copy link
Collaborator

Copy link
Member

@mcollinamcollina left a comment

Choose a reason for hiding this comment

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

lgtm

@joyeecheungjoyeecheung added commit-queue Add this label to land a pull request using GitHub Actions. commit-queue-squash Add this label to instruct the Commit Queue to squash all the PR commits into the first one. labels Mar 4, 2025
@nodejs-github-botnodejs-github-bot removed the commit-queue Add this label to land a pull request using GitHub Actions. label Mar 4, 2025
joyeecheung added a commit to joyeecheung/node that referenced this pull request Aug 17, 2025
This handles two cases caused by using Promise.all() with multiple dynamic import() that can make an asynchronously linked module job that has finished/failed linking but has not yet started actual evaluation appear in the load cache when another require request is in turn to handle it. - When the cached async job has finished linking but has not started its evaluation because the async run() task would be later in line, start the evaluation from require() with runSync(). - When the cached async job have already encountered a linking error that gets wrapped into a rejection, but is still later in line to throw on it, just unwrap and throw the linking error from require(). PR-URL: nodejs#57187Fixes: nodejs#57172 Refs: shufo/prettier-plugin-blade#311 Refs: https://github.com/fisker/prettier-plugin-blade-311 Refs: mochajs/mocha#5290 Refs: https://github.com/JoshuaKGoldberg/repros/tree/mocha-missing-module-cyclic Reviewed-By: Guy Bedford <[email protected]> Reviewed-By: Matteo Collina <[email protected]>
joyeecheung added a commit to joyeecheung/node that referenced this pull request Aug 17, 2025
When require(esm) encounters a cached module job that is instantiated but not yet evaluated, run the evaluation. This catches an edge case previously missed in nodejs#57187. PR-URL: nodejs#58067Fixes: nodejs#58061 Reviewed-By: Jacob Smith <[email protected]>
joyeecheung added a commit to joyeecheung/node that referenced this pull request Aug 18, 2025
This handles two cases caused by using Promise.all() with multiple dynamic import() that can make an asynchronously linked module job that has finished/failed linking but has not yet started actual evaluation appear in the load cache when another require request is in turn to handle it. - When the cached async job has finished linking but has not started its evaluation because the async run() task would be later in line, start the evaluation from require() with runSync(). - When the cached async job have already encountered a linking error that gets wrapped into a rejection, but is still later in line to throw on it, just unwrap and throw the linking error from require(). PR-URL: nodejs#57187Fixes: nodejs#57172 Refs: shufo/prettier-plugin-blade#311 Refs: https://github.com/fisker/prettier-plugin-blade-311 Refs: mochajs/mocha#5290 Refs: https://github.com/JoshuaKGoldberg/repros/tree/mocha-missing-module-cyclic Reviewed-By: Guy Bedford <[email protected]> Reviewed-By: Matteo Collina <[email protected]>
joyeecheung added a commit to joyeecheung/node that referenced this pull request Aug 18, 2025
When require(esm) encounters a cached module job that is instantiated but not yet evaluated, run the evaluation. This catches an edge case previously missed in nodejs#57187. PR-URL: nodejs#58067Fixes: nodejs#58061 Reviewed-By: Jacob Smith <[email protected]>
joyeecheung added a commit to joyeecheung/node that referenced this pull request Aug 19, 2025
This handles two cases caused by using Promise.all() with multiple dynamic import() that can make an asynchronously linked module job that has finished/failed linking but has not yet started actual evaluation appear in the load cache when another require request is in turn to handle it. - When the cached async job has finished linking but has not started its evaluation because the async run() task would be later in line, start the evaluation from require() with runSync(). - When the cached async job have already encountered a linking error that gets wrapped into a rejection, but is still later in line to throw on it, just unwrap and throw the linking error from require(). PR-URL: nodejs#57187Fixes: nodejs#57172 Refs: shufo/prettier-plugin-blade#311 Refs: https://github.com/fisker/prettier-plugin-blade-311 Refs: mochajs/mocha#5290 Refs: https://github.com/JoshuaKGoldberg/repros/tree/mocha-missing-module-cyclic Reviewed-By: Guy Bedford <[email protected]> Reviewed-By: Matteo Collina <[email protected]>
joyeecheung added a commit to joyeecheung/node that referenced this pull request Aug 19, 2025
When require(esm) encounters a cached module job that is instantiated but not yet evaluated, run the evaluation. This catches an edge case previously missed in nodejs#57187. PR-URL: nodejs#58067Fixes: nodejs#58061 Reviewed-By: Jacob Smith <[email protected]>
marco-ippolito pushed a commit to joyeecheung/node that referenced this pull request Aug 20, 2025
This handles two cases caused by using Promise.all() with multiple dynamic import() that can make an asynchronously linked module job that has finished/failed linking but has not yet started actual evaluation appear in the load cache when another require request is in turn to handle it. - When the cached async job has finished linking but has not started its evaluation because the async run() task would be later in line, start the evaluation from require() with runSync(). - When the cached async job have already encountered a linking error that gets wrapped into a rejection, but is still later in line to throw on it, just unwrap and throw the linking error from require(). PR-URL: nodejs#57187Fixes: nodejs#57172 Refs: shufo/prettier-plugin-blade#311 Refs: https://github.com/fisker/prettier-plugin-blade-311 Refs: mochajs/mocha#5290 Refs: https://github.com/JoshuaKGoldberg/repros/tree/mocha-missing-module-cyclic Reviewed-By: Guy Bedford <[email protected]> Reviewed-By: Matteo Collina <[email protected]>
marco-ippolito pushed a commit to joyeecheung/node that referenced this pull request Aug 20, 2025
When require(esm) encounters a cached module job that is instantiated but not yet evaluated, run the evaluation. This catches an edge case previously missed in nodejs#57187. PR-URL: nodejs#58067Fixes: nodejs#58061 Reviewed-By: Jacob Smith <[email protected]>
marco-ippolito pushed a commit that referenced this pull request Aug 20, 2025
This handles two cases caused by using Promise.all() with multiple dynamic import() that can make an asynchronously linked module job that has finished/failed linking but has not yet started actual evaluation appear in the load cache when another require request is in turn to handle it. - When the cached async job has finished linking but has not started its evaluation because the async run() task would be later in line, start the evaluation from require() with runSync(). - When the cached async job have already encountered a linking error that gets wrapped into a rejection, but is still later in line to throw on it, just unwrap and throw the linking error from require(). PR-URL: #57187 Backport-PR-URL: #59504Fixes: #57172 Refs: shufo/prettier-plugin-blade#311 Refs: https://github.com/fisker/prettier-plugin-blade-311 Refs: mochajs/mocha#5290 Refs: https://github.com/JoshuaKGoldberg/repros/tree/mocha-missing-module-cyclic Reviewed-By: Guy Bedford <[email protected]> Reviewed-By: Matteo Collina <[email protected]> Refs: #52697
marco-ippolito pushed a commit that referenced this pull request Aug 20, 2025
When require(esm) encounters a cached module job that is instantiated but not yet evaluated, run the evaluation. This catches an edge case previously missed in #57187. PR-URL: #58067 Backport-PR-URL: #59504Fixes: #58061 Reviewed-By: Jacob Smith <[email protected]> Refs: #52697
marco-ippolito pushed a commit that referenced this pull request Aug 20, 2025
This handles two cases caused by using Promise.all() with multiple dynamic import() that can make an asynchronously linked module job that has finished/failed linking but has not yet started actual evaluation appear in the load cache when another require request is in turn to handle it. - When the cached async job has finished linking but has not started its evaluation because the async run() task would be later in line, start the evaluation from require() with runSync(). - When the cached async job have already encountered a linking error that gets wrapped into a rejection, but is still later in line to throw on it, just unwrap and throw the linking error from require(). PR-URL: #57187 Backport-PR-URL: #59504Fixes: #57172 Refs: shufo/prettier-plugin-blade#311 Refs: https://github.com/fisker/prettier-plugin-blade-311 Refs: mochajs/mocha#5290 Refs: https://github.com/JoshuaKGoldberg/repros/tree/mocha-missing-module-cyclic Reviewed-By: Guy Bedford <[email protected]> Reviewed-By: Matteo Collina <[email protected]> Refs: #52697
marco-ippolito pushed a commit that referenced this pull request Aug 20, 2025
When require(esm) encounters a cached module job that is instantiated but not yet evaluated, run the evaluation. This catches an edge case previously missed in #57187. PR-URL: #58067 Backport-PR-URL: #59504Fixes: #58061 Reviewed-By: Jacob Smith <[email protected]> Refs: #52697
marco-ippolito pushed a commit that referenced this pull request Aug 23, 2025
This handles two cases caused by using Promise.all() with multiple dynamic import() that can make an asynchronously linked module job that has finished/failed linking but has not yet started actual evaluation appear in the load cache when another require request is in turn to handle it. - When the cached async job has finished linking but has not started its evaluation because the async run() task would be later in line, start the evaluation from require() with runSync(). - When the cached async job have already encountered a linking error that gets wrapped into a rejection, but is still later in line to throw on it, just unwrap and throw the linking error from require(). PR-URL: #57187 Backport-PR-URL: #59504Fixes: #57172 Refs: shufo/prettier-plugin-blade#311 Refs: https://github.com/fisker/prettier-plugin-blade-311 Refs: mochajs/mocha#5290 Refs: https://github.com/JoshuaKGoldberg/repros/tree/mocha-missing-module-cyclic Reviewed-By: Guy Bedford <[email protected]> Reviewed-By: Matteo Collina <[email protected]> Refs: #52697
marco-ippolito pushed a commit that referenced this pull request Aug 23, 2025
When require(esm) encounters a cached module job that is instantiated but not yet evaluated, run the evaluation. This catches an edge case previously missed in #57187. PR-URL: #58067 Backport-PR-URL: #59504Fixes: #58061 Reviewed-By: Jacob Smith <[email protected]> Refs: #52697
marco-ippolito pushed a commit that referenced this pull request Aug 25, 2025
This handles two cases caused by using Promise.all() with multiple dynamic import() that can make an asynchronously linked module job that has finished/failed linking but has not yet started actual evaluation appear in the load cache when another require request is in turn to handle it. - When the cached async job has finished linking but has not started its evaluation because the async run() task would be later in line, start the evaluation from require() with runSync(). - When the cached async job have already encountered a linking error that gets wrapped into a rejection, but is still later in line to throw on it, just unwrap and throw the linking error from require(). PR-URL: #57187 Backport-PR-URL: #59504Fixes: #57172 Refs: shufo/prettier-plugin-blade#311 Refs: https://github.com/fisker/prettier-plugin-blade-311 Refs: mochajs/mocha#5290 Refs: https://github.com/JoshuaKGoldberg/repros/tree/mocha-missing-module-cyclic Reviewed-By: Guy Bedford <[email protected]> Reviewed-By: Matteo Collina <[email protected]> Refs: #52697
marco-ippolito pushed a commit that referenced this pull request Aug 25, 2025
When require(esm) encounters a cached module job that is instantiated but not yet evaluated, run the evaluation. This catches an edge case previously missed in #57187. PR-URL: #58067 Backport-PR-URL: #59504Fixes: #58061 Reviewed-By: Jacob Smith <[email protected]> Refs: #52697
marco-ippolito pushed a commit that referenced this pull request Aug 25, 2025
This handles two cases caused by using Promise.all() with multiple dynamic import() that can make an asynchronously linked module job that has finished/failed linking but has not yet started actual evaluation appear in the load cache when another require request is in turn to handle it. - When the cached async job has finished linking but has not started its evaluation because the async run() task would be later in line, start the evaluation from require() with runSync(). - When the cached async job have already encountered a linking error that gets wrapped into a rejection, but is still later in line to throw on it, just unwrap and throw the linking error from require(). PR-URL: #57187 Backport-PR-URL: #59504Fixes: #57172 Refs: shufo/prettier-plugin-blade#311 Refs: https://github.com/fisker/prettier-plugin-blade-311 Refs: mochajs/mocha#5290 Refs: https://github.com/JoshuaKGoldberg/repros/tree/mocha-missing-module-cyclic Reviewed-By: Guy Bedford <[email protected]> Reviewed-By: Matteo Collina <[email protected]> Refs: #52697
marco-ippolito pushed a commit that referenced this pull request Aug 25, 2025
When require(esm) encounters a cached module job that is instantiated but not yet evaluated, run the evaluation. This catches an edge case previously missed in #57187. PR-URL: #58067 Backport-PR-URL: #59504Fixes: #58061 Reviewed-By: Jacob Smith <[email protected]> Refs: #52697
marco-ippolito pushed a commit that referenced this pull request Aug 25, 2025
This handles two cases caused by using Promise.all() with multiple dynamic import() that can make an asynchronously linked module job that has finished/failed linking but has not yet started actual evaluation appear in the load cache when another require request is in turn to handle it. - When the cached async job has finished linking but has not started its evaluation because the async run() task would be later in line, start the evaluation from require() with runSync(). - When the cached async job have already encountered a linking error that gets wrapped into a rejection, but is still later in line to throw on it, just unwrap and throw the linking error from require(). PR-URL: #57187 Backport-PR-URL: #59504Fixes: #57172 Refs: shufo/prettier-plugin-blade#311 Refs: https://github.com/fisker/prettier-plugin-blade-311 Refs: mochajs/mocha#5290 Refs: https://github.com/JoshuaKGoldberg/repros/tree/mocha-missing-module-cyclic Reviewed-By: Guy Bedford <[email protected]> Reviewed-By: Matteo Collina <[email protected]> Refs: #52697
marco-ippolito pushed a commit that referenced this pull request Aug 25, 2025
When require(esm) encounters a cached module job that is instantiated but not yet evaluated, run the evaluation. This catches an edge case previously missed in #57187. PR-URL: #58067 Backport-PR-URL: #59504Fixes: #58061 Reviewed-By: Jacob Smith <[email protected]> Refs: #52697
marco-ippolito pushed a commit that referenced this pull request Aug 25, 2025
This handles two cases caused by using Promise.all() with multiple dynamic import() that can make an asynchronously linked module job that has finished/failed linking but has not yet started actual evaluation appear in the load cache when another require request is in turn to handle it. - When the cached async job has finished linking but has not started its evaluation because the async run() task would be later in line, start the evaluation from require() with runSync(). - When the cached async job have already encountered a linking error that gets wrapped into a rejection, but is still later in line to throw on it, just unwrap and throw the linking error from require(). PR-URL: #57187 Backport-PR-URL: #59504Fixes: #57172 Refs: shufo/prettier-plugin-blade#311 Refs: https://github.com/fisker/prettier-plugin-blade-311 Refs: mochajs/mocha#5290 Refs: https://github.com/JoshuaKGoldberg/repros/tree/mocha-missing-module-cyclic Reviewed-By: Guy Bedford <[email protected]> Reviewed-By: Matteo Collina <[email protected]> Refs: #52697
marco-ippolito pushed a commit that referenced this pull request Aug 25, 2025
When require(esm) encounters a cached module job that is instantiated but not yet evaluated, run the evaluation. This catches an edge case previously missed in #57187. PR-URL: #58067 Backport-PR-URL: #59504Fixes: #58061 Reviewed-By: Jacob Smith <[email protected]> Refs: #52697
marco-ippolito pushed a commit that referenced this pull request Aug 26, 2025
This handles two cases caused by using Promise.all() with multiple dynamic import() that can make an asynchronously linked module job that has finished/failed linking but has not yet started actual evaluation appear in the load cache when another require request is in turn to handle it. - When the cached async job has finished linking but has not started its evaluation because the async run() task would be later in line, start the evaluation from require() with runSync(). - When the cached async job have already encountered a linking error that gets wrapped into a rejection, but is still later in line to throw on it, just unwrap and throw the linking error from require(). PR-URL: #57187 Backport-PR-URL: #59504Fixes: #57172 Refs: shufo/prettier-plugin-blade#311 Refs: https://github.com/fisker/prettier-plugin-blade-311 Refs: mochajs/mocha#5290 Refs: https://github.com/JoshuaKGoldberg/repros/tree/mocha-missing-module-cyclic Reviewed-By: Guy Bedford <[email protected]> Reviewed-By: Matteo Collina <[email protected]> Refs: #52697
marco-ippolito pushed a commit that referenced this pull request Aug 26, 2025
When require(esm) encounters a cached module job that is instantiated but not yet evaluated, run the evaluation. This catches an edge case previously missed in #57187. PR-URL: #58067 Backport-PR-URL: #59504Fixes: #58061 Reviewed-By: Jacob Smith <[email protected]> Refs: #52697
marco-ippolito pushed a commit that referenced this pull request Aug 27, 2025
This handles two cases caused by using Promise.all() with multiple dynamic import() that can make an asynchronously linked module job that has finished/failed linking but has not yet started actual evaluation appear in the load cache when another require request is in turn to handle it. - When the cached async job has finished linking but has not started its evaluation because the async run() task would be later in line, start the evaluation from require() with runSync(). - When the cached async job have already encountered a linking error that gets wrapped into a rejection, but is still later in line to throw on it, just unwrap and throw the linking error from require(). PR-URL: #57187 Backport-PR-URL: #59504Fixes: #57172 Refs: shufo/prettier-plugin-blade#311 Refs: https://github.com/fisker/prettier-plugin-blade-311 Refs: mochajs/mocha#5290 Refs: https://github.com/JoshuaKGoldberg/repros/tree/mocha-missing-module-cyclic Reviewed-By: Guy Bedford <[email protected]> Reviewed-By: Matteo Collina <[email protected]> Refs: #52697
marco-ippolito pushed a commit that referenced this pull request Aug 27, 2025
When require(esm) encounters a cached module job that is instantiated but not yet evaluated, run the evaluation. This catches an edge case previously missed in #57187. PR-URL: #58067 Backport-PR-URL: #59504Fixes: #58061 Reviewed-By: Jacob Smith <[email protected]> Refs: #52697
@marco-ippolitomarco-ippolito removed the lts-watch-v20.x PRs that may need to be released in v20.x label Nov 19, 2025
Sign up for freeto join this conversation on GitHub. Already have an account? Sign in to comment

Labels

backport-requested-v20.xPRs awaiting manual backport to the v20.x-staging branch.c++Issues and PRs that require attention from people who are familiar with C++.commit-queue-squashAdd this label to instruct the Commit Queue to squash all the PR commits into the first one.esmIssues and PRs related to the ECMAScript Modules implementation.needs-ciPRs that need a full CI run.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Mocha ERR_REQUIRE_CYCLE_MODULE on latest CJS

6 participants

@joyeecheung@nodejs-github-bot@marco-ippolito@mcollina@guybedford@aduh95