Open rotu opened 2 weeks ago
Related: #51093 (the debugger falsely believes promise rejections in web streams are unhandled).
I believe skipFiles
is controlled by VSCode, not Node.js, though I could be mistaken.
Issue #51093 reports to a problem in the V8 Debugger, whereas this one is about an issue in VSCode. I was thinking of #53732, whoops.
Yes, skipFiles
is controlled by VSCode. And the Chrome debugger supports a similar "ignore list" feature. Adding the pattern ^node:internal
does not seem to suppress the debugger. Even adding the pattern .*
does not seem to suppress the debugger from breaking here!
If there's a more correct way to suppress these exceptions, please let me know!
It sounds to me like this is a VSCode bug, and not a Node.js one, but again, I may be mistaken.
I'm not sure what the pattern is supposedly matching against when the debugger is deciding whether to pause at a breakpoint - the stack traces in play (shown above) seem to match ignore-listed scripts, so I'd expect there not to be a breakpoint hit.
It sounds to me like this is a VSCode bug, and not a Node.js one, but again, I may be mistaken.
Might be, but if so, then Chrome has the same behavior. I can't tell if there's any workaround to #53732 that allows trapping uncaught exceptions without making async code to "nuisance trip" the debugger on an eventually-handled rejection.
@nodejs/v8 - is this a V8 debugger issue, or a VSCode/Chrome issue?
The promise is rejected at writableStreamRejectCloseAndClosedPromiseIfNeeded
and is then immediately handled to prevent it from having an unhandled rejection which is why Node.js doesn't report an unhandled rejection here:
stream[kIsClosedPromise].reject(stream[kState]?.storedError);
setPromiseHandled(stream[kIsClosedPromise].promise);
The other instances I can trigger this are similar:
stream[kIsClosedPromise].reject(error);
setPromiseHandled(stream[kIsClosedPromise].promise);
This indeed seems overly eager (and incorrect) on V8's side. Additionally skipFiles not working seems like a VSCode bug.
I'll open a PR with a workaround from our side until it's addressed there. Those aren't real errors.
Given the discussion on the linked PR, it seems like this isn't actionable from Node.js's side. I suggest opening an issue with VSCode / V8.
Feel free to close the issue if you agree
Reverting close state to author's close
This isn't closed, the fact skipFiles does not suppress exceptions in core and that it's still a caught exception is a bug and we should track it since it affects our users even if we can't fix it.
we should track it since it affects our users even if we can't fix it
Is this being tracked anywhere that it is actionable? From what I can tell, V8's trackers show related issues as (mostly) fixed
, although it appears otherwise.
FWIW, there's a v8 internal API that is likely relevant here...v8::Promise::MarkAsSilent
... https://v8docs.nodesource.com/node-22.4/d3/d8f/classv8_1_1_promise.html#a085c428f3f9600830a87819f99e9fda6 ... there is likely a performance impact but might be possible for us to expose this API to JavaScript internally.
@jasnell Interesting. I'm not sure that's going to help in this case. At least for the web streams API, these rejected promises should trip the debugger if the user await
s them (but V8 doesn't do this - it only trips the debugger when the promise becomes rejected, if at all).
Given the way the promises work, if I await a promise it creates a new promise... So the pattern here is:
Create P1 ... Mark P1 as handled and silent. If P1 rejects then debugger will not be triggered.
Await P1... Creates P2
When P1 rejects, P2 is rejected. P2 is not marked as handled or silent, so P2 will trigger the debugger. I think this is exactly the pattern that is needed here.
@jasnell I wish it worked that way, and I believe that is the way it works for unhandledrejection
events.
The debugger, on the other hand, seems to trip based on the explicit throw
or Promise.reject
call from user code.
All the below examples create unhandled rejections. IMO they should all pause the debugger:
const x = Promise.reject(new Error("on purpose"))
// these trip the debugger:
const a1 = x.catch((e) => { throw e })
const a2 = x.catch((e) => { Promise.reject(e) })
// none of these trip the debugger:
const b1 = x.then()
const b2 = Promise.resolve(x)
const b3 = new Promise((resolve) => { resolve(x) })
const b4 = Promise.resolve().then(() => x)
const b5 = Promise.resolve({ then: (resolve) => resolve(x) })
const b6 = (async () => { await x })()
// none of these trip the debugger either:
const b7 = new Promise((resolve, reject) => { x.then(resolve, reject) })
const b8 = x.catch(
(e) => ({
then: (resolve, reject) => { reject(e) }
})
)
const b9 = x.catch(Promise.reject.bind(Promise))
edit: even worse the above cases only report the stack trace where the Error
object was created (at least with node flags set to --unhandled-rejections=warn --trace-warnings --trace-uncaught --report-uncaught-exception --async-stack-traces
) and the stack trace to emitUnhandledRejectionWarning
. From the output you can't tell which line is responsible for which warning! Even --trace-promises
doesn't help much.
Version
22.4.1
Platform
Subsystem
No response
What steps will reproduce the bug?
Run this code in the VSCode debugger with
"skipFiles":["<node_internals>/**"]
in the launch configuration.How often does it reproduce? Is there a required condition?
No response
What is the expected behavior? Why is that the expected behavior?
It is expected that the debugger won't trip. This promise rejection is entirely created and handled within node's internals and should be suppressed by the VSCode's
skipFiles
filter.What do you see instead?
The debugger breaks when the promise is rejected.
The trace (given by
console.trace
) when it is rejected looks like:The stack on the error object is:
Additional information
No response