GV

GeezerVison

The World Wide Web, restored to 800 pixels of glory.

Home|About|Gaming cabinet|Archives|How it works|Starter sites

On this site

  • Sign in
  • ActionsCI/CD and automation
  • CodespacesCloud development environments
  • All Features
  • Documentation
  • Training
  • Certifications
  • Build with Copilot
  • Customize Copilot
  • Integrations
  • Trending repos
  • GitHub Sponsors
  • Developer articles
  • Enterprises

This view

  • Original page
  • New address

GeezerVison is showing github.com — Move ESM loaders off-thread by JakobJingleheimer · Pull Request #44710 · nodejs/node

Conversation

@JakobJingleheimer

@JakobJingleheimer JakobJingleheimer commented Sep 18, 2022 •
edited by aduh95
Loading

Uh oh!

There was an error while loading. Please reload this page.

Copy link
Copy Markdown
Member

Resolves #43658

To-dos:

  • Address Module serialisation issue (Proxies aren’t serialisable)
  • Spawn hooks worker only when custom hooks are supplied
  • Convert ESM defaultResolve to synchronous (no real change there)
  • Convert import.meta.resolve() to synchronous
    • When no custom hooks are supplied, just regular sync
    • When any custom hooks are supplied, leverage worker + atomics to simulate sync
  • Handle very large request/response payload (main ↔︎ worker)

Notable changes:

Custom ESM loader hooks run on dedicated thread

ESM hooks supplied via loaders (--experimental-loader=./foo.mjs) now run in a dedicated thread, isolated from the main thread. This provides a separate scope for loaders and ensures no cross-contamination between loaders and application code. A few things to know:

  • Communication between application code and loaders code must now be done via the globalPreload hook’s port. Global variables are not shared between scopes.
  • The dedicated worker thread is spun up only when at least one custom loader is provided.
  • There is an overhead cost to spinning up this thread, increasing both startup time and memory consumption. These are non-trivial, but likely not burdensome.

Synchronous import.meta.resolve()

In alignment with browser behavior, this function now returns synchronously. Despite this, user loader resolve hooks can still be defined as async functions (or as sync functions, if the author prefers). Even when there are async resolve hooks loaded, import.meta.resolve will still return synchronously for application code.

Contributed by Anna Henningsen, Antoine du Hamel, Geoffrey Booth, Guy Bedford, Jacob Smith, and Michaël Zasso in #44710

Sorry, something went wrong.

Uh oh!

There was an error while loading. Please reload this page.

bricss, shrujalshah28, mcollina, lin72h, TerminallyDied, trivikr, zyhou, MomenNano, robpalme, and LongTengDao reacted with thumbs up emoji tonivj5 and bricss reacted with eyes emoji
All reactions
  • 👍 10 reactions
  • 👀 2 reactions
@JakobJingleheimer JakobJingleheimer added this to the ESM Loaders: Stable (CJS parity) milestone Sep 18, 2022
@nodejs-github-bot

nodejs-github-bot commented Sep 18, 2022

Copy link
Copy Markdown
Collaborator

Review requested:

  • @nodejs/modules
All reactions

Sorry, something went wrong.

Uh oh!

There was an error while loading. Please reload this page.

@nodejs-github-bot nodejs-github-bot added lib / src Issues and PRs involving general changes in the lib/ or src/ directories. needs-ci PRs that need a full CI run. labels Sep 18, 2022
GeoffreyBooth
GeoffreyBooth reviewed Sep 18, 2022
View reviewed changes
Comment thread lib/internal/modules/esm/public_loader_proxy.js Resolved

Uh oh!

There was an error while loading. Please reload this page.

Comment thread lib/internal/modules/esm/public_loader_proxy.js Resolved

Uh oh!

There was an error while loading. Please reload this page.

Comment thread lib/internal/modules/esm/public_loader_proxy.js Resolved

Uh oh!

There was an error while loading. Please reload this page.

Comment thread lib/internal/modules/esm/public_loader_proxy.js Resolved

Uh oh!

There was an error while loading. Please reload this page.

Comment thread lib/internal/modules/esm/public_loader_proxy.js Resolved

Uh oh!

There was an error while loading. Please reload this page.

Comment thread lib/internal/modules/esm/public_loader_proxy.js Resolved

Uh oh!

There was an error while loading. Please reload this page.

Comment thread lib/internal/modules/esm/public_loader_proxy.js Resolved

Uh oh!

There was an error while loading. Please reload this page.

Comment thread lib/internal/modules/esm/worker.js Resolved

Uh oh!

There was an error while loading. Please reload this page.

Comment thread lib/internal/modules/esm/worker.js Resolved

Uh oh!

There was an error while loading. Please reload this page.

Comment thread lib/internal/process/esm_loader.js Resolved

Uh oh!

There was an error while loading. Please reload this page.

@GeoffreyBooth GeoffreyBooth added esm Issues and PRs related to the ECMAScript Modules implementation. loaders Issues and PRs related to ES module loaders. labels Sep 18, 2022
GeoffreyBooth
GeoffreyBooth reviewed Sep 18, 2022
View reviewed changes
Comment thread lib/internal/modules/esm/public_loader_proxy.js Resolved

Uh oh!

There was an error while loading. Please reload this page.

MoLow
MoLow reviewed Sep 19, 2022
View reviewed changes
Comment thread lib/internal/modules/esm/public_loader_proxy.js Resolved

Uh oh!

There was an error while loading. Please reload this page.

MoLow
MoLow reviewed Sep 19, 2022
View reviewed changes
Comment thread lib/internal/modules/esm/worker.js Resolved

Uh oh!

There was an error while loading. Please reload this page.

MoLow
MoLow reviewed Sep 19, 2022
View reviewed changes
Comment thread lib/internal/process/esm_loader.js Resolved

Uh oh!

There was an error while loading. Please reload this page.

MoLow
MoLow reviewed Sep 19, 2022
View reviewed changes
Comment thread lib/internal/modules/esm/worker.js Resolved

Uh oh!

There was an error while loading. Please reload this page.

@GeoffreyBooth GeoffreyBooth mentioned this pull request Sep 19, 2022
test: add test for Module._stat #44713
Merged
@JakobJingleheimer
JakobJingleheimer force-pushed the feat/esm_off-thread-loaders branch 3 times, most recently from 2265b79 to ce94ba6 Compare September 19, 2022 21:17
aduh95
aduh95 reviewed Sep 20, 2022
View reviewed changes
Comment thread lib/internal/modules/esm/public_loader_proxy.js Resolved

Uh oh!

There was an error while loading. Please reload this page.

Comment thread lib/internal/modules/esm/public_loader_proxy.js Resolved

Uh oh!

There was an error while loading. Please reload this page.

Comment thread lib/internal/modules/esm/worker.js Resolved

Uh oh!

There was an error while loading. Please reload this page.

Comment thread lib/internal/modules/esm/public_loader_proxy.js Resolved

Uh oh!

There was an error while loading. Please reload this page.

Comment thread lib/internal/modules/esm/worker.js Resolved

Uh oh!

There was an error while loading. Please reload this page.

Comment thread lib/internal/modules/esm/worker.js Resolved

Uh oh!

There was an error while loading. Please reload this page.

@JakobJingleheimer JakobJingleheimer mentioned this pull request Sep 20, 2022
Modules loaded through --import seem to use a separate ESM cache #44583
Closed
dygabo
dygabo reviewed Sep 23, 2022
View reviewed changes
Comment thread lib/internal/worker.js Resolved

Uh oh!

There was an error while loading. Please reload this page.

@cspotcode

cspotcode commented Sep 27, 2022

Copy link
Copy Markdown

Does each worker thread get its own loader thread? E.g. if an app launches 2x worker threads, there will be 6x threads total: one main, 2x worker, 3x loader?

All reactions

Sorry, something went wrong.

Uh oh!

There was an error while loading. Please reload this page.

@JakobJingleheimer

JakobJingleheimer commented Sep 27, 2022

Copy link
Copy Markdown
Member Author

The current design puts all loaders in the same worker thread (eg there are a total of 2 threads: the main and the worker). Internal things are handled via the main thread; userland things are handled by the worker thread.

All reactions

Sorry, something went wrong.

Uh oh!

There was an error while loading. Please reload this page.

@cspotcode

cspotcode commented Sep 27, 2022

Copy link
Copy Markdown

I think that's slightly different than what I'm asking about. If a node app uses the worker threads API to launch worker threads, does each get its own loader thread? (where each loader thread may have one or more loaders running within it)

Not does each loader get a thread, but does each user thread get a loader thread.

All reactions

Sorry, something went wrong.

Uh oh!

There was an error while loading. Please reload this page.

@GeoffreyBooth

GeoffreyBooth commented Sep 27, 2022

Copy link
Copy Markdown
Member

If a node app uses the worker threads API to launch worker threads, does each get its own loader thread?

I think the current state (before this PR) is that loaders always execute in the main thread, even if they’re “for” user code that is in a worker thread. So I would think that after this PR, loaders would run inside their worker thread, regardless of whether they’re customizing main or worker thread user code.

So in other words, for an app using custom loaders where user code that spawns three workers, there are five threads total: main, loaders, and the three workers spawned by the user code.

This is just my guess, others can please correct me if I’m mistaken.

All reactions

Sorry, something went wrong.

Uh oh!

There was an error while loading. Please reload this page.

@targos

targos commented Sep 27, 2022

Copy link
Copy Markdown
Member

I don't know how it's implemented but I would expect that each Node.js thread (main and workers) has its own separate loaders thread, at least for two reasons:

  • Worker threads are supposed to be as isolated as possible from the main thread and from each other
  • You can spawn a worker thread with a different set of --loader flags.
aduh95 and JakobJingleheimer reacted with thumbs up emoji
All reactions
  • 👍 2 reactions

Sorry, something went wrong.

Uh oh!

There was an error while loading. Please reload this page.

@bmeck

bmeck commented Sep 27, 2022 via email

Copy link
Copy Markdown
Member
First pass this seems fine but likely should investigate a thread per loader URL/some key. Having it per thread they instrument would be more costly if you spawn/tear down threads.
…
On Tue, Sep 27, 2022, 1:24 PM Michaël Zasso ***@***.***> wrote: I don't know how it's implemented but I would expect that each Node.js thread (main and workers) has its own separate loaders thread, at least for two reasons: - Worker threads are supposed to be as isolated as possible from the main thread and from each other - You can spawn a worker thread with a different set of --loader flags. — Reply to this email directly, view it on GitHub <#44710 (comment)>, or unsubscribe <https://github.com/notifications/unsubscribe-auth/AABZJI7YII3ZMWWAGIMOC6LWAM3WJANCNFSM6AAAAAAQPNEM7E> . You are receiving this because you are on a team that was mentioned.Message ID: ***@***.***>
JakobJingleheimer and targos reacted with thumbs up emoji JakobJingleheimer reacted with heart emoji
All reactions
  • 👍 2 reactions
  • ❤️ 1 reaction

Sorry, something went wrong.

Uh oh!

There was an error while loading. Please reload this page.

@targos

targos commented Oct 8, 2022

Copy link
Copy Markdown
Member

What remains to be done?

All reactions

Sorry, something went wrong.

Uh oh!

There was an error while loading. Please reload this page.

@JakobJingleheimer

JakobJingleheimer commented Oct 8, 2022

Copy link
Copy Markdown
Member Author

Something in this implementation is causing node to hang on startup. We suspect it's some kind of circular dependency, and the subsequent dependency smacks into the Atomics lock (preventing the rest of the flow to complete, which would release the initial lock). I think this circular dep is between ESMLoader and Worker (it's rather difficult to troubleshoot as output is swallowed).

There is a working PoC, so we know this works in principle (and that the Atomics otherwise behave appropriately, so the problem lies with integrating it into node).

This was on hold whilst I was on holiday. I'm working on it today and tomorrow (but will be away on a business trip next week).

targos reacted with thumbs up emoji
All reactions
  • 👍 1 reaction

Sorry, something went wrong.

Uh oh!

There was an error while loading. Please reload this page.

@JakobJingleheimer

JakobJingleheimer commented Oct 8, 2022

Copy link
Copy Markdown
Member Author

I found the circular dep:

process/esm_loader.js:12 ← modules/esm/public_loader_proxy.js:3 ← worker_thread.js:29/36 ← process/pre_execution.js:562 ← process/esm_loader.js

aduh95 and dbauszus-glx reacted with hooray emoji
All reactions
  • 🎉 2 reactions

Sorry, something went wrong.

Uh oh!

There was an error while loading. Please reload this page.

@JakobJingleheimer

JakobJingleheimer commented Oct 9, 2022 •
edited
Loading

Uh oh!

There was an error while loading. Please reload this page.

Copy link
Copy Markdown
Member Author

Gah, it wasn't a circular dependency 🤦‍♂️ it was setting url to null here:

https://github.com/nodejs/node/blob/9836c67198b2d01350f283e10b872849e81abefa/lib/internal/worker.js#L141-L149

which caused the worker to spawn with nothing in it.

(I shouldn't have copied it over from the previous attempt at off-threading, which was later hard-coding the url).

I'm not sure if the internal worker should go through the classic path, or merely get require()ed (I tried both, and it doesn't seem to make a difference).

There is (now was) a circular dependency issue after the empty worker issue is addressed in

https://github.com/nodejs/node/blob/9836c67198b2d01350f283e10b872849e81abefa/lib/internal/main/worker_thread.js#L142

that's now fixed too.

NODE_DEBUG=esm,worker ./out/Release/node -p 1 still doesn't print 1, BUT it does now print debugging output, and we can see it getting fairly far along now, so progress:

ESM 13018: hooking up public ESMLoader
ESM 13018: creating worker for publicESMLoader
[…]
WORKER 15812: instantiating Worker. url: file:///…/nodejs/node/lib/internal/modules/esm/worker.js doEval: classic
WORKER 15812: [0] created Worker with ID 1

esm/worker.js's debug (worker for public ESM running) is not printing, so that's likely why things have stalled (as soon as the worker is running, it releases the lock).

All reactions

Sorry, something went wrong.

Uh oh!

There was an error while loading. Please reload this page.

JakobJingleheimer
JakobJingleheimer commented Oct 9, 2022
View reviewed changes
Comment thread lib/internal/modules/esm/public_loader_proxy.js Resolved

Uh oh!

There was an error while loading. Please reload this page.

@nodejs-github-bot

nodejs-github-bot commented Apr 13, 2023

Copy link
Copy Markdown
Collaborator

CI: https://ci.nodejs.org/job/node-test-pull-request/51192/

All reactions

Sorry, something went wrong.

Uh oh!

There was an error while loading. Please reload this page.

@aduh95

aduh95 commented Apr 13, 2023

Copy link
Copy Markdown
Contributor

Landed in 4667b07

MoLow, JakobJingleheimer, debadree25, RaisinTen, lin72h, and GeoffreyBooth reacted with rocket emoji
All reactions
  • 🚀 6 reactions

Sorry, something went wrong.

Uh oh!

There was an error while loading. Please reload this page.

@giltayar

giltayar commented Apr 13, 2023

Copy link
Copy Markdown
Contributor

@JakobJingleheimer @aduh95 Oh, shit. This is merged? 😅 What version will it appear in? I want to check the testdouble loader to see if my changes to support off-thread loaders work.

JakobJingleheimer, debadree25, and Nkmol reacted with heart emoji
All reactions
  • ❤️ 3 reactions

Sorry, something went wrong.

Uh oh!

There was an error while loading. Please reload this page.

@MoLow

MoLow commented Apr 13, 2023

Copy link
Copy Markdown
Member

@giltayar it will (hopefully) land on v20, see #47381 (comment)

JakobJingleheimer and debadree25 reacted with eyes emoji
All reactions
  • 👀 2 reactions

Sorry, something went wrong.

Uh oh!

There was an error while loading. Please reload this page.

@debadree25

debadree25 commented Apr 13, 2023

Copy link
Copy Markdown
Contributor

kudos to everyone involved! This was a mammoth task! 7 months and 100+ commits 😳🙏!

JakobJingleheimer and MomenNano reacted with heart emoji Nkmol, lin72h, and MomenNano reacted with rocket emoji
All reactions
  • ❤️ 2 reactions
  • 🚀 3 reactions

Sorry, something went wrong.

Uh oh!

There was an error while loading. Please reload this page.

@lgarron

lgarron commented Apr 13, 2023 •
edited
Loading

Uh oh!

There was an error while loading. Please reload this page.

Copy link
Copy Markdown

I'm so glad to see this land! 😻

I maintain a test suite for interoperable path resolution1, and I'm able to confirm that 4667b07 is compatible with browsers for synchronous import.meta.resolve(…). 🥳

Footnotes

  1. https://github.com/lgarron/loadeverything.net/blob/c08bf22cdd4ed6dc56f7c7e43727cd95f0c3b886/Makefile#L28 ↩

debadree25, lin72h, and GeoffreyBooth reacted with rocket emoji
All reactions
  • 🚀 3 reactions

Sorry, something went wrong.

Uh oh!

There was an error while loading. Please reload this page.

This was referenced Apr 13, 2023
import.meta.resolve is now defined to be _sync_ wooorm/import-meta-resolve#14
Closed
fix #14: make resolve synchronous wooorm/import-meta-resolve#15
Merged
@github-actions github-actions Bot mentioned this pull request Apr 15, 2023
CI Reliability 2023-04-15 nodejs/reliability#543
Open
33 tasks
@tniessen tniessen mentioned this pull request Apr 15, 2023
Flaky es-module/test-esm-loader-side-effect (and other ESM loader tests) #47571
Closed
@merceyz merceyz mentioned this pull request Apr 15, 2023
Using custom loaders results in a load loop / OOM #47566
Closed
@regseb regseb mentioned this pull request Apr 18, 2023
Enabling import meta resolve by default? nodejs/modules#550
Closed
@joyeecheung joyeecheung mentioned this pull request Apr 18, 2023
esm: do not lazy instantiate loaders to avoid infinite loop #47599
Closed
@ai ai mentioned this pull request Apr 23, 2023
Node.js 20 support lukeed/tsm#46
Open
@JakobJingleheimer JakobJingleheimer mentioned this pull request Apr 24, 2023
How to run the Node built-in testrunner for TypeScript files inside a specific directory? nodejs/help#3902
Closed
@cjihrig cjihrig mentioned this pull request Apr 27, 2023
ESM loaders cannot be defined via Worker option execArgv in v20 #47747
Closed
@koshic koshic mentioned this pull request May 4, 2023
Node 20.x: import.meta.resolve is not a promise anymore iambumblehead/esmock#200
Closed
@climba03003 climba03003 mentioned this pull request May 5, 2023
Node 20 auto-loader not able to transpile TS to JS when using ts-node/esm fastify/fastify-autoload#309
Closed
2 tasks done
@targos targos mentioned this pull request May 5, 2023
Node 20 ESM loaders cannot hook into the CommonJS loader #47880
Closed
@mxschmitt mxschmitt mentioned this pull request May 11, 2023
[Bug] add Node 20 to CI matrix microsoft/playwright#22582
Closed
@Zowilder77 Zowilder77 mentioned this pull request May 11, 2023
Review requested:https://github.com/nodejs/node/pull/44710#issue-1377029159 nodejs/Release#866
Closed
1 task
@iambumblehead iambumblehead mentioned this pull request May 16, 2023
build: run tests on node.js v21 nightly iambumblehead/esmock#199
Merged
@iambumblehead

iambumblehead commented May 16, 2023

Copy link
Copy Markdown

What is the recommended way to pass data from the main thread to a loader? Thanks in advance for any reply

All reactions

Sorry, something went wrong.

Uh oh!

There was an error while loading. Please reload this page.

@iambumblehead

iambumblehead commented May 16, 2023

Copy link
Copy Markdown

I see it is documented here (sorry for bothering) https://nodejs.org/api/esm.html#globalpreload I'll play around with this

porsager and vjpr reacted with thumbs up emoji
All reactions
  • 👍 2 reactions

Sorry, something went wrong.

Uh oh!

There was an error while loading. Please reload this page.

@kirrg001 kirrg001 mentioned this pull request Jun 20, 2023
ESM loader v20.3.0 access parent path nodejs/help#4190
Closed
@kirrg001

kirrg001 commented Jun 20, 2023

Copy link
Copy Markdown

We are unable to access the app path with ESM loaders being off threaded in v20 easily. See nodejs/help#4190
Wondering if you are aware of a better solution than the one I've shared in the help issue. Thanks

All reactions

Sorry, something went wrong.

Uh oh!

There was an error while loading. Please reload this page.

@lukeed

lukeed commented Jun 27, 2023

Copy link
Copy Markdown

Looks like loaders no longer have access to process.argv

All reactions

Sorry, something went wrong.

Uh oh!

There was an error while loading. Please reload this page.

@JakobJingleheimer

JakobJingleheimer commented Jun 27, 2023 •
edited
Loading

Uh oh!

There was an error while loading. Please reload this page.

Copy link
Copy Markdown
Member Author

Looks like loaders no longer have access to process.argv

Correct; that is how worker threads work. There is a dedicated issue for this as well as a proposal to address (which is already linked).

I'm locking this as most of the recent comments are telling us the sky is blue.

All reactions

Sorry, something went wrong.

Uh oh!

There was an error while loading. Please reload this page.

@ruyadorno

ruyadorno commented Jun 29, 2023

Copy link
Copy Markdown
Member

It looks like this should be marked as a semver-major and we should avoid backporting it to v18.x, @nodejs/loaders thoughts?

All reactions

Sorry, something went wrong.

Uh oh!

There was an error while loading. Please reload this page.

@aduh95

aduh95 commented Jun 29, 2023 •
edited
Loading

Uh oh!

There was an error while loading. Please reload this page.

Copy link
Copy Markdown
Contributor

Marking it as semver-major would be wrong I think, because it's an experimental API, but it could be labelled as dont-land-on-v18.x though.
That being said, I think we do want to backport it to v18.x, but that cannot happen until the ecosystem is ready for it, so backport-requested is actually the correct place for this PR to be atm if we follow that plan.

All reactions

Sorry, something went wrong.

Uh oh!

There was an error while loading. Please reload this page.

Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.

Reviewers

@ljharb ljharb ljharb left review comments
@ronag ronag ronag left review comments
@joyeecheung joyeecheung joyeecheung left review comments
@MoLow MoLow MoLow left review comments
@aduh95 aduh95 aduh95 left review comments
@RafaelGSS RafaelGSS RafaelGSS left review comments
@GeoffreyBooth GeoffreyBooth GeoffreyBooth approved these changes
@anonrig anonrig anonrig approved these changes
@mcollina mcollina Awaiting requested review from mcollina
+4 more reviewers
@targos targos targos left review comments
@nicolo-ribaudo nicolo-ribaudo nicolo-ribaudo left review comments
@jlenon7 jlenon7 jlenon7 left review comments
@dygabo dygabo dygabo left review comments
Reviewers whose approvals may not affect merge requirements

Uh oh!

There was an error while loading. Please reload this page.

Assignees

No one assigned

Labels

author ready PRs with CI started, the required approvals, and no outstanding review comments. esm Issues and PRs related to the ECMAScript Modules implementation. lib / src Issues and PRs involving general changes in the lib/ or src/ directories. loaders Issues and PRs related to ES module loaders. needs-ci PRs that need a full CI run. notable-change PRs with changes that should be highlighted in changelogs.

Projects

None yet

Milestone

ESM Loaders: Stable (CJS parity)

Development

Successfully merging this pull request may close these issues.

esm, loader: move to own thread

Uh oh!

There was an error while loading. Please reload this page.

19 participants

@JakobJingleheimer @nodejs-github-bot @cspotcode @GeoffreyBooth @targos @bmeck @aduh95 @giltayar @mcollina @joyeecheung @MoLow @debadree25 @lgarron @iambumblehead @kirrg001 @lukeed @ruyadorno @ljharb @anonrig
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
You are visitor 01402503 — GeezerVison does not store the pages you look up.
Best viewed with Netscape Navigator 4.7 or Internet Explorer 6 at 800×600. No cookies required.