Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Stalled top-level promise detection for TLA #42868

Closed
zuozp8 opened this issue Apr 25, 2022 · 17 comments
Closed

Stalled top-level promise detection for TLA #42868

zuozp8 opened this issue Apr 25, 2022 · 17 comments
Labels
confirmed-bug Issues with confirmed bugs.

Comments

@zuozp8
Copy link

zuozp8 commented Apr 25, 2022

Version

v18.0.0

Subsystem

modules

What steps will reproduce the bug?

create file 1.mjs with following content

await new Promise(() => {})

run it and check code returned by process with node 1.mjs && echo $?

What is the expected behavior?

I would like to be verbousely warned that script didn't reach it's end

What do you see instead?

process finished with error code 13, with nothing in console

> node 1.mjs && echo $?
13
@RaisinTen
Copy link
Contributor

Isn't this expected behavior?

// Handle a Promise from running code that potentially does Top-Level Await.
// In that case, it makes sense to set the exit code to a specific non-zero
// value if the main code never finishes running.
function handleProcessExit() {
process.exitCode ??= 13;
}

@zuozp8
Copy link
Author

zuozp8 commented Apr 25, 2022

You're right, it seems that node recognizes that there are no pending handlers and closes, i got the same result from code

await new Promise(r => setTimeout(r, 100).unref())

for me return code 13 (that usually means ACCESS_DENIED in linux) was suspicious, I expected it to come from kernel

Printing there a warning (like we had with UnhandledPromiseRejectionWarning) would be nice

@targos
Copy link
Member

targos commented Apr 25, 2022

All possible Node.js exit codes are documented in https://nodejs.org/api/process.html#exit-codes
It seems they are just assigned sequentially.

@RaisinTen
Copy link
Contributor

Printing there a warning (like we had with UnhandledPromiseRejectionWarning) would be nice

Would you like to send a PR to implement that?

@nodejs/loaders does this sound like a good idea?

@JakobJingleheimer
Copy link
Member

Sorry, how is this related to loaders? Is it happening inside a custom loader? I don't see anything here to suggest it is—but if it is, I'm wondering if this is something I might catch in #42623.

@RaisinTen
Copy link
Contributor

I'm sorry, I meant to ping @nodejs/modules because of

| `src/module_wrap.*`, `lib/internal/modules/*`, `lib/internal/vm/module.js` | @nodejs/modules |
. Please ignore my previous ping.

@zuozp8
Copy link
Author

zuozp8 commented Apr 26, 2022

I can prepare PR after modules team approves the idea

@zuozp8 zuozp8 changed the title crash with error code 13 when deadlocked deadlock in .mjs file causes node to exit without warning Apr 26, 2022
@devsnek
Copy link
Member

devsnek commented Apr 26, 2022

you might need to revive this change in v8: https://chromium-review.googlesource.com/c/v8/v8/+/2341765

@guybedford
Copy link
Contributor

@devsnek what would it take to revive that? @mhdawson @jasnell does Node.js have a process for directing development work on V8 where it does not directly align with the needs of Chrome? There's a few modules things like this, and this is a great example of differing needs of V8 for Chrome and the needs of V8 for Node.js (like modules GC and other hooks).

@devsnek
Copy link
Member

devsnek commented Apr 29, 2022

fwiw they were not against that change, i just lost track of working on it. i'm sure they'd be happy if someone were to CL an updated version.

@mhdawson
Copy link
Member

@guybedford I don't think we have a process. In the past I think individuals like @joyeecheung and possible @legendecas have submitted PRs and stickhandled them through the V8 process. @devsnek mentions generally V8 has accepted changes.

@guybedford guybedford added the confirmed-bug Issues with confirmed bugs. label Apr 29, 2022
@guybedford guybedford changed the title deadlock in .mjs file causes node to exit without warning Stalled top-level promise detection for TLA Apr 29, 2022
@guybedford
Copy link
Contributor

I guess the best we can do is clearly mark the direction here then. I've updated the title and added the confirmed bug label, and further suggestions welcome as well.

Top-level await stalled promise handling should be supported by V8. The V8 solution to this problem has already been worked on by @devsnek in #42868 (comment), but it needs further work to land.

@bnoordhuis
Copy link
Member

Changes to V8 are not necessary per se, the pending promise can be found with the inspector's Runtime.queryObjects RPC method. I did a proof of concept in 2019: https://gist.github.com/bnoordhuis/e4f935a0c81c477533c9bdc54b22e837

@joyeecheung
Copy link
Member

does Node.js have a process for directing development work on V8 where it does not directly align with the needs of Chrome? There's a few modules things like this, and this is a great example of differing needs of V8 for Chrome and the needs of V8 for Node.js (like modules GC and other hooks).

I think the general convention is that once we confirm a V8 change is necessary (e.g. can reproduce with a V8-only test) we open a bug in the V8 issue tracker and maybe ping someone who's the owner of related V8 components. V8's issue tracker has a NodeJS-Hotlist label for Node.js-related requests. Then someone may take that bug from there.

@joyeecheung
Copy link
Member

joyeecheung commented May 5, 2022

Also about the bug here specifically - I can try to take a look at the bug and the CL and see if I can revive it, as @devsnek suggested. (From a quick glance I think having a V8 API for this case has its merits, as the inspector isn't always available in all of our builds)

@joyeecheung
Copy link
Member

FYI I have modified the v8 patch a bit and re-uploaded it in the original CL - https://chromium-review.googlesource.com/c/v8/v8/+/2341765/ may need to take some time to take care of the dynamic import use cases mentioned in the previous reviews though.

@joyeecheung
Copy link
Member

joyeecheung commented Mar 2, 2024

I have a prototype here which prints this for the repro in the OP

Error: Detected unfinished top-level await at file:///Users/joyee/projects/node/unfinished.mjs:1
await new Promise(() => {});
^

For imported unfinished TLA:

Error: Detected unfinished top-level await at file:///Users/joyee/projects/node/mod.mjs:1
await new Promise(() => { 'mod' });
^

(By the time we exit and check for the unfinished TLA, the stack is already lost. I guess it's better than printing nothing at all).

joyeecheung added a commit that referenced this issue Mar 10, 2024
PR-URL: #51999
Fixes: #42868
Reviewed-By: Moshe Atlow <moshe@atlow.co.il>
Reviewed-By: Benjamin Gruenbaum <benjamingr@gmail.com>
Reviewed-By: Geoffrey Booth <webadmin@geoffreybooth.com>
joyeecheung added a commit that referenced this issue Mar 10, 2024
When the entry point is a module and the graph it imports still
contains unsettled top-level await when the Node.js instance
finishes the event loop, search from the entry point module
for unsettled top-level await and print their location.

To avoid unnecessary overhead, we register a promise that only
gets settled when the entry point graph evaluation returns
from await, and only search the module graph if it's still
unsettled by the time the instance is exiting.

This patch only handles this for entry point modules. Other kinds of
modules are more complicated so will be left for the future.

Drive-by: update the terminology "unfinished promise" to the
more correct one "unsettled promise" in the codebase.

PR-URL: #51999
Fixes: #42868
Reviewed-By: Moshe Atlow <moshe@atlow.co.il>
Reviewed-By: Benjamin Gruenbaum <benjamingr@gmail.com>
Reviewed-By: Geoffrey Booth <webadmin@geoffreybooth.com>
rdw-msft pushed a commit to rdw-msft/node that referenced this issue Mar 26, 2024
Split the `internal/process/esm_loader` file which contains the
singleton cascaded loader:

- The the singleton cascaded loader now directly resides in
  `internal/modules/esm/loader`, where the constructor also lives.
  This file is the root of most circular dependency of ESM code,
  (because components of the loader need the singleton itself),
  so this makes the dependency more obvious. Added comments about
  loading it lazily to avoid circular dependency.
- The getter to the cascaded loader is also turned into a method
  to make the side effect explicit.
- The sequence of `loadESM()` and `handleMainPromise` is now merged
  together into `runEntryPointWithESMLoader()` in
  `internal/modules/run_main` because this is intended to run entry
  points with the ESM loader and not just any module.
- Documents how top-level await is handled.

PR-URL: nodejs#51999
Fixes: nodejs#42868
Reviewed-By: Moshe Atlow <moshe@atlow.co.il>
Reviewed-By: Benjamin Gruenbaum <benjamingr@gmail.com>
Reviewed-By: Geoffrey Booth <webadmin@geoffreybooth.com>
rdw-msft pushed a commit to rdw-msft/node that referenced this issue Mar 26, 2024
PR-URL: nodejs#51999
Fixes: nodejs#42868
Reviewed-By: Moshe Atlow <moshe@atlow.co.il>
Reviewed-By: Benjamin Gruenbaum <benjamingr@gmail.com>
Reviewed-By: Geoffrey Booth <webadmin@geoffreybooth.com>
rdw-msft pushed a commit to rdw-msft/node that referenced this issue Mar 26, 2024
When the entry point is a module and the graph it imports still
contains unsettled top-level await when the Node.js instance
finishes the event loop, search from the entry point module
for unsettled top-level await and print their location.

To avoid unnecessary overhead, we register a promise that only
gets settled when the entry point graph evaluation returns
from await, and only search the module graph if it's still
unsettled by the time the instance is exiting.

This patch only handles this for entry point modules. Other kinds of
modules are more complicated so will be left for the future.

Drive-by: update the terminology "unfinished promise" to the
more correct one "unsettled promise" in the codebase.

PR-URL: nodejs#51999
Fixes: nodejs#42868
Reviewed-By: Moshe Atlow <moshe@atlow.co.il>
Reviewed-By: Benjamin Gruenbaum <benjamingr@gmail.com>
Reviewed-By: Geoffrey Booth <webadmin@geoffreybooth.com>
marco-ippolito pushed a commit that referenced this issue May 2, 2024
Split the `internal/process/esm_loader` file which contains the
singleton cascaded loader:

- The the singleton cascaded loader now directly resides in
  `internal/modules/esm/loader`, where the constructor also lives.
  This file is the root of most circular dependency of ESM code,
  (because components of the loader need the singleton itself),
  so this makes the dependency more obvious. Added comments about
  loading it lazily to avoid circular dependency.
- The getter to the cascaded loader is also turned into a method
  to make the side effect explicit.
- The sequence of `loadESM()` and `handleMainPromise` is now merged
  together into `runEntryPointWithESMLoader()` in
  `internal/modules/run_main` because this is intended to run entry
  points with the ESM loader and not just any module.
- Documents how top-level await is handled.

PR-URL: #51999
Fixes: #42868
Reviewed-By: Moshe Atlow <moshe@atlow.co.il>
Reviewed-By: Benjamin Gruenbaum <benjamingr@gmail.com>
Reviewed-By: Geoffrey Booth <webadmin@geoffreybooth.com>
marco-ippolito pushed a commit that referenced this issue May 2, 2024
PR-URL: #51999
Fixes: #42868
Reviewed-By: Moshe Atlow <moshe@atlow.co.il>
Reviewed-By: Benjamin Gruenbaum <benjamingr@gmail.com>
Reviewed-By: Geoffrey Booth <webadmin@geoffreybooth.com>
jcbhmr pushed a commit to jcbhmr/node that referenced this issue May 15, 2024
Split the `internal/process/esm_loader` file which contains the
singleton cascaded loader:

- The the singleton cascaded loader now directly resides in
  `internal/modules/esm/loader`, where the constructor also lives.
  This file is the root of most circular dependency of ESM code,
  (because components of the loader need the singleton itself),
  so this makes the dependency more obvious. Added comments about
  loading it lazily to avoid circular dependency.
- The getter to the cascaded loader is also turned into a method
  to make the side effect explicit.
- The sequence of `loadESM()` and `handleMainPromise` is now merged
  together into `runEntryPointWithESMLoader()` in
  `internal/modules/run_main` because this is intended to run entry
  points with the ESM loader and not just any module.
- Documents how top-level await is handled.

PR-URL: nodejs#51999
Fixes: nodejs#42868
Reviewed-By: Moshe Atlow <moshe@atlow.co.il>
Reviewed-By: Benjamin Gruenbaum <benjamingr@gmail.com>
Reviewed-By: Geoffrey Booth <webadmin@geoffreybooth.com>
jcbhmr pushed a commit to jcbhmr/node that referenced this issue May 15, 2024
PR-URL: nodejs#51999
Fixes: nodejs#42868
Reviewed-By: Moshe Atlow <moshe@atlow.co.il>
Reviewed-By: Benjamin Gruenbaum <benjamingr@gmail.com>
Reviewed-By: Geoffrey Booth <webadmin@geoffreybooth.com>
jcbhmr pushed a commit to jcbhmr/node that referenced this issue May 15, 2024
When the entry point is a module and the graph it imports still
contains unsettled top-level await when the Node.js instance
finishes the event loop, search from the entry point module
for unsettled top-level await and print their location.

To avoid unnecessary overhead, we register a promise that only
gets settled when the entry point graph evaluation returns
from await, and only search the module graph if it's still
unsettled by the time the instance is exiting.

This patch only handles this for entry point modules. Other kinds of
modules are more complicated so will be left for the future.

Drive-by: update the terminology "unfinished promise" to the
more correct one "unsettled promise" in the codebase.

PR-URL: nodejs#51999
Fixes: nodejs#42868
Reviewed-By: Moshe Atlow <moshe@atlow.co.il>
Reviewed-By: Benjamin Gruenbaum <benjamingr@gmail.com>
Reviewed-By: Geoffrey Booth <webadmin@geoffreybooth.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
confirmed-bug Issues with confirmed bugs.
Projects
None yet
Development

Successfully merging a pull request may close this issue.

9 participants