Skip to content

[Flight] Track Debug Info from Synchronously Unwrapped Promises #33485

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

Open
wants to merge 24 commits into
base: main
Choose a base branch
from

Conversation

sebmarkbage
Copy link
Collaborator

Stacked on #33482.

There's a flaw with getting information from the execution context of the ping. For the soft-deprecated "throw a promise" technique, this is a bit unreliable because you could in theory throw the same one multiple times. Similarly, a more fundamental flaw with that API is that it doesn't allow for tracking the information of Promises that are already synchronously able to resolve.

This stops tracking the async debug info in the case of throwing a Promise and only when you render a Promise. That means some loss of data but we should just warn for throwing a Promise anyway.

Instead, this also adds support for tracking use()d thenables and forwarding _debugInfo from then. This is done by extracting the info from the Promise after the fact instead of in the resolve so that it only happens once at the end after the pings are done.

This also supports passing the same Promise in multiple places and tracking the debug info at each location, even if it was already instrumented with a synchronous value by the time of the second use.

@github-actions github-actions bot added the React Core Team Opened by a member of the React Core Team label Jun 9, 2025
@react-sizebot
Copy link

react-sizebot commented Jun 9, 2025

Comparing: c38e268...2a18f3e

Critical size changes

Includes critical production bundles, as well as any change greater than 2%:

Name +/- Base Current +/- gzip Base gzip Current gzip
oss-stable/react-dom/cjs/react-dom.production.js = 6.68 kB 6.68 kB = 1.83 kB 1.83 kB
oss-stable/react-dom/cjs/react-dom-client.production.js = 530.58 kB 530.07 kB = 93.66 kB 93.57 kB
oss-experimental/react-dom/cjs/react-dom.production.js = 6.69 kB 6.69 kB = 1.83 kB 1.83 kB
oss-experimental/react-dom/cjs/react-dom-client.production.js = 651.67 kB 651.16 kB = 114.77 kB 114.69 kB
facebook-www/ReactDOM-prod.classic.js = 676.61 kB 676.11 kB = 119.05 kB 118.97 kB
facebook-www/ReactDOM-prod.modern.js = 666.89 kB 666.39 kB = 117.44 kB 117.36 kB

Significant size changes

Includes any change greater than 0.2%:

Expand to show
Name +/- Base Current +/- gzip Base gzip Current gzip
oss-experimental/react-server-dom-esm/cjs/react-server-dom-esm-server.node.development.js +1.74% 169.60 kB 172.56 kB +1.26% 31.61 kB 32.01 kB
oss-experimental/react-server-dom-parcel/cjs/react-server-dom-parcel-server.node.development.js +1.70% 174.27 kB 177.22 kB +1.29% 32.06 kB 32.48 kB
oss-experimental/react-server-dom-webpack/cjs/react-server-dom-webpack-server.node.unbundled.development.js +1.69% 181.00 kB 184.06 kB +1.25% 33.13 kB 33.55 kB
oss-experimental/react-server-dom-turbopack/cjs/react-server-dom-turbopack-server.node.development.js +1.68% 182.15 kB 185.21 kB +1.26% 33.42 kB 33.84 kB
oss-experimental/react-server-dom-webpack/cjs/react-server-dom-webpack-server.node.development.js +1.68% 182.21 kB 185.26 kB +1.25% 33.43 kB 33.85 kB
oss-experimental/react-server-dom-esm/cjs/react-server-dom-esm-client.browser.development.js +1.47% 114.37 kB 116.05 kB +0.65% 21.54 kB 21.68 kB
oss-experimental/react-server-dom-parcel/cjs/react-server-dom-parcel-client.browser.development.js +1.47% 114.72 kB 116.41 kB +0.58% 21.48 kB 21.60 kB
oss-experimental/react-client/cjs/react-client-flight.development.js +1.45% 116.02 kB 117.70 kB +0.69% 21.30 kB 21.45 kB
oss-experimental/react-server-dom-turbopack/cjs/react-server-dom-turbopack-client.browser.development.js +1.45% 116.55 kB 118.24 kB +0.61% 21.94 kB 22.07 kB
oss-experimental/react-server-dom-webpack/cjs/react-server-dom-webpack-client.browser.development.js +1.44% 117.11 kB 118.79 kB +0.71% 22.07 kB 22.23 kB
oss-experimental/react-server-dom-esm/cjs/react-server-dom-esm-client.node.development.js +1.42% 118.96 kB 120.65 kB +0.55% 22.34 kB 22.46 kB
oss-experimental/react-server-dom-parcel/cjs/react-server-dom-parcel-client.edge.development.js +1.40% 120.25 kB 121.94 kB +0.64% 22.68 kB 22.83 kB
oss-experimental/react-server-dom-parcel/cjs/react-server-dom-parcel-client.node.development.js +1.39% 121.49 kB 123.18 kB +0.75% 22.78 kB 22.95 kB
oss-experimental/react-server-dom-turbopack/cjs/react-server-dom-turbopack-client.edge.development.js +1.37% 123.35 kB 125.03 kB +0.63% 23.18 kB 23.33 kB
oss-experimental/react-server-dom-webpack/cjs/react-server-dom-webpack-client.edge.development.js +1.37% 123.44 kB 125.12 kB +0.63% 23.21 kB 23.36 kB
oss-experimental/react-server-dom-turbopack/cjs/react-server-dom-turbopack-client.node.development.js +1.35% 124.42 kB 126.10 kB +0.73% 23.29 kB 23.46 kB
oss-experimental/react-server-dom-webpack/cjs/react-server-dom-webpack-client.node.unbundled.development.js +1.33% 126.77 kB 128.46 kB +0.72% 23.53 kB 23.70 kB
oss-experimental/react-server-dom-webpack/cjs/react-server-dom-webpack-client.node.development.js +1.32% 128.10 kB 129.79 kB +0.71% 23.78 kB 23.95 kB
oss-experimental/react-server/cjs/react-server-flight.development.js +1.15% 116.65 kB 118.00 kB +0.93% 21.56 kB 21.76 kB
oss-experimental/react-server-dom-turbopack/cjs/react-server-dom-turbopack-server.browser.development.js +0.92% 163.29 kB 164.79 kB +0.67% 30.15 kB 30.36 kB
oss-experimental/react-server-dom-webpack/cjs/react-server-dom-webpack-server.browser.development.js +0.91% 163.84 kB 165.33 kB +0.67% 30.28 kB 30.48 kB
oss-experimental/react-server-dom-parcel/cjs/react-server-dom-parcel-server.browser.development.js +0.90% 155.55 kB 156.95 kB +0.72% 28.79 kB 29.00 kB
oss-experimental/react-server-dom-webpack/cjs/react-server-dom-webpack-server.edge.development.js +0.89% 167.22 kB 168.72 kB +0.75% 30.70 kB 30.93 kB
oss-experimental/react-server-dom-turbopack/cjs/react-server-dom-turbopack-server.edge.development.js +0.89% 167.25 kB 168.75 kB +0.74% 30.70 kB 30.92 kB
oss-experimental/react-server-dom-parcel/cjs/react-server-dom-parcel-server.edge.development.js +0.88% 159.47 kB 160.87 kB +0.69% 29.32 kB 29.52 kB
oss-stable-semver/react-server/cjs/react-server-flight.development.js +0.60% 109.46 kB 110.12 kB +0.39% 20.27 kB 20.34 kB
oss-stable/react-server/cjs/react-server-flight.development.js +0.60% 109.46 kB 110.12 kB +0.39% 20.27 kB 20.34 kB
oss-stable-semver/react-server-dom-turbopack/cjs/react-server-dom-turbopack-server.browser.development.js +0.52% 156.08 kB 156.89 kB +0.35% 28.87 kB 28.97 kB
oss-stable/react-server-dom-turbopack/cjs/react-server-dom-turbopack-server.browser.development.js +0.52% 156.08 kB 156.89 kB +0.35% 28.87 kB 28.97 kB
oss-experimental/react-markup/cjs/react-markup.react-server.development.js +0.52% 584.66 kB 587.70 kB +0.30% 104.61 kB 104.92 kB
oss-stable-semver/react-server-dom-webpack/cjs/react-server-dom-webpack-server.browser.development.js +0.52% 156.63 kB 157.44 kB +0.32% 29.00 kB 29.09 kB
oss-stable/react-server-dom-webpack/cjs/react-server-dom-webpack-server.browser.development.js +0.52% 156.63 kB 157.44 kB +0.32% 29.00 kB 29.09 kB
oss-stable-semver/react-server-dom-webpack/cjs/react-server-dom-webpack-server.edge.development.js +0.51% 159.66 kB 160.47 kB +0.31% 29.37 kB 29.46 kB
oss-stable/react-server-dom-webpack/cjs/react-server-dom-webpack-server.edge.development.js +0.51% 159.66 kB 160.47 kB +0.31% 29.37 kB 29.46 kB
oss-stable-semver/react-server-dom-turbopack/cjs/react-server-dom-turbopack-server.edge.development.js +0.51% 159.69 kB 160.50 kB +0.31% 29.37 kB 29.46 kB
oss-stable/react-server-dom-turbopack/cjs/react-server-dom-turbopack-server.edge.development.js +0.51% 159.69 kB 160.50 kB +0.31% 29.37 kB 29.46 kB
oss-stable-semver/react-server-dom-webpack/cjs/react-server-dom-webpack-server.node.unbundled.development.js +0.49% 164.12 kB 164.93 kB +0.33% 29.89 kB 29.99 kB
oss-stable/react-server-dom-webpack/cjs/react-server-dom-webpack-server.node.unbundled.development.js +0.49% 164.12 kB 164.93 kB +0.33% 29.89 kB 29.99 kB
oss-stable-semver/react-server-dom-turbopack/cjs/react-server-dom-turbopack-server.node.development.js +0.49% 165.27 kB 166.08 kB +0.34% 30.16 kB 30.26 kB
oss-stable/react-server-dom-turbopack/cjs/react-server-dom-turbopack-server.node.development.js +0.49% 165.27 kB 166.08 kB +0.34% 30.16 kB 30.26 kB
oss-stable-semver/react-server-dom-webpack/cjs/react-server-dom-webpack-server.node.development.js +0.49% 165.32 kB 166.13 kB +0.32% 30.18 kB 30.28 kB
oss-stable/react-server-dom-webpack/cjs/react-server-dom-webpack-server.node.development.js +0.49% 165.32 kB 166.13 kB +0.32% 30.18 kB 30.28 kB
oss-stable-semver/react-server-dom-parcel/cjs/react-server-dom-parcel-server.browser.development.js +0.48% 148.34 kB 149.06 kB +0.29% 27.51 kB 27.59 kB
oss-stable/react-server-dom-parcel/cjs/react-server-dom-parcel-server.browser.development.js +0.48% 148.34 kB 149.06 kB +0.29% 27.51 kB 27.59 kB
oss-stable-semver/react-server-dom-parcel/cjs/react-server-dom-parcel-server.edge.development.js +0.47% 151.91 kB 152.63 kB +0.26% 28.02 kB 28.09 kB
oss-stable/react-server-dom-parcel/cjs/react-server-dom-parcel-server.edge.development.js +0.47% 151.91 kB 152.63 kB +0.26% 28.02 kB 28.09 kB
oss-stable-semver/react-server-dom-esm/cjs/react-server-dom-esm-server.node.development.js +0.47% 152.71 kB 153.42 kB +0.38% 28.36 kB 28.46 kB
oss-stable/react-server-dom-esm/cjs/react-server-dom-esm-server.node.development.js +0.47% 152.71 kB 153.42 kB +0.38% 28.36 kB 28.46 kB
oss-stable-semver/react-server-dom-parcel/cjs/react-server-dom-parcel-server.node.development.js +0.45% 157.38 kB 158.09 kB +0.34% 28.82 kB 28.92 kB
oss-stable/react-server-dom-parcel/cjs/react-server-dom-parcel-server.node.development.js +0.45% 157.38 kB 158.09 kB +0.34% 28.82 kB 28.92 kB
oss-experimental/react-server-dom-esm/esm/react-server-dom-esm-client.browser.development.js +0.26% 153.96 kB 154.36 kB +0.31% 35.92 kB 36.03 kB

Generated by 🚫 dangerJS against 2a18f3e

// This is a temporary fork of the `use` implementation until we accept
// promises everywhere.
const thenable: Thenable<mixed> = (wakeable: any);
switch (thenable.status) {
case 'fulfilled':
case 'fulfilled': {
forwardDebugInfoFromThenable(request, task, thenable);
Copy link
Collaborator Author

Choose a reason for hiding this comment

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

This only applies to the internal temporary wrapper of Flight.

With this approach the Promise inside React.lazy is never tracked since it disappears and it behaves more like the throwing-a-promise approach. The idea is to convert these to just return Promises eventually though.

You can still assign manual _debugInfo to a lazy though.

However, we also only track Lazy in the child position. Lazy element.type (the only documented approach) are not tracked. So if you have some I/O to load some module with a Component in it, that wouldn't be tracked atm.

forwardDebugInfo(request, newTask, debugInfo);
}
}
forwardDebugInfoFromCurrentContext(request, newTask, thenable);
Copy link
Collaborator Author

Choose a reason for hiding this comment

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

We should just call forwardDebugInfoFromPromise here. The difference is that if this is not actually a native Promise but is resolved by a thenable which is in turn resolved by I/O (like the test in AsyncDebugInfo-test) or a native Promise then we can pick it up from the context it is resolved in.

The problem is just that now it's inconsistent because such as Thenable will only forward the information if it wasn't already resolved. If it was sync, the information would get lost.

So this is a bit inconsistent about when we expect Thenables to be manually instrumented with debugInfo like the ones coming out of Flight and when we can pick it up automatically.

The ones from FlightClient are also inconsistent in that they may pick up the I/O of the stream that loaded them when they weren't sync but if they were sync then only the forwarded debugInfo from the other server is included.

@sebmarkbage sebmarkbage force-pushed the iosync branch 3 times, most recently from dc02618 to 06d85b7 Compare June 10, 2025 00:14
sebmarkbage and others added 20 commits June 9, 2025 22:05
This is tricky because there can be parallel visits and really we should
probably generate a list, sort and then emit the sorted list.
Only count something as contributing to the children end time if it was actually logged

This excludes timing of promises that we didn't log an await for such as microtasks.

Avoids bumping too many parallel levels.
promiseResolve, for already resolved promises, is invoked at the end of
its execution phase. That means that any awaits that happen inside would
have their start time below the end time which cuts them off.
```
-1..........-2.....------
---3....................-
```
advanceTaskTime should be used before an operation to advance time before
emitting that operation.

markOperationEndTime should be used as its own marker after an operation.
This needs to always be emitted even if it doesn't advance time.
An async function's last piece of code doesn't have any way to trace it
back to the last await it was waiting for inside the body. Instead we
guess this from whatever executed last.
…itted

This lets us include zero width entries for parallel things that weren't
blocking but might still be interesting.
We visit all the previous nodes before making any decisions about what to
include so we potentially visit a lot of nodes over and over.

We can optimize a bit by just stopping for anything that finished before
the request start.
Unfortunately there's no public API to get to the internal symbols but
AsyncResource is conceptually the same class so we can repurpose its
method that does the same thing.
So might look for it on thenables when passed to use().
Before we wrap it in a Lazy since it won't be picked up by the lazy.
If we're forwarding debugInfo and also visiting the underlying
native Promise, make sure we're not emitting the same debugInfo twice.
This will be used to look at the diff to the next commit
The owner/stack disappears in this case because the use() is not a real await.
Then pass this as the default if there is no deeper await.

This means that use() behaves similar to await.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
CLA Signed React Core Team Opened by a member of the React Core Team
Projects
None yet
Development

Successfully merging this pull request may close these issues.

4 participants