Skip to content

[Flight] Forward debugInfo from awaited instrumented Promises #33415

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

Merged
merged 8 commits into from
Jun 4, 2025

Conversation

sebmarkbage
Copy link
Collaborator

@sebmarkbage sebmarkbage commented Jun 3, 2025

Stacked on #33403.

When a Promise is coming from React such as when it's passed from another environment, we should forward the debug information from that environment. We already do that when rendered as a child.

This makes it possible to also await promise and have the information from that instrumented promise carry through to the next render.

This is a bit tricky because the current protocol is that we have to read it from the Promise after it resolves so it has time to be assigned to the promise. async_hooks doesn't pass us the instance (even though it has it) when it gets resolved so we need to keep it around. However, we have to be very careful because if we get this wrong it'll cause a memory leak since we retain things by asyncId and then manually listen for destroy() which can only be called once a Promise is GC:ed, which it can't be if we retain it. We have to therefore use a WeakRef in case it never resolves, and then read the _debugInfo when it resolves. We could maybe install a setter or something instead but that's also heavy.

The other issues is that we don't use native Promises in ReactFlightClient so our instrumented promises aren't picked up by the async_hooks implementation and so we never get a handle to our thenable instance. To solve this we can create a native wrapper only in DEV.

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

react-sizebot commented Jun 3, 2025

Comparing: 1540081...3e749b9

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 = 529.82 kB 529.82 kB = 93.51 kB 93.51 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 = 650.91 kB 650.91 kB = 114.64 kB 114.64 kB
facebook-www/ReactDOM-prod.classic.js = 675.86 kB 675.86 kB = 118.91 kB 118.91 kB
facebook-www/ReactDOM-prod.modern.js = 666.14 kB 666.14 kB = 117.30 kB 117.30 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.10% 165.71 kB 167.53 kB +1.11% 30.94 kB 31.28 kB
oss-experimental/react-server-dom-parcel/cjs/react-server-dom-parcel-server.node.development.js +1.10% 165.73 kB 167.55 kB +1.12% 30.93 kB 31.27 kB
oss-experimental/react-server-dom-webpack/cjs/react-server-dom-webpack-server.node.unbundled.development.js +1.06% 172.40 kB 174.22 kB +1.07% 31.99 kB 32.33 kB
oss-experimental/react-server-dom-turbopack/cjs/react-server-dom-turbopack-server.node.development.js +1.05% 173.54 kB 175.36 kB +1.05% 32.28 kB 32.62 kB
oss-experimental/react-server-dom-webpack/cjs/react-server-dom-webpack-server.node.development.js +1.05% 173.61 kB 175.43 kB +1.05% 32.30 kB 32.64 kB
oss-experimental/react-server-dom-esm/esm/react-server-dom-esm-client.browser.development.js +0.54% 153.08 kB 153.90 kB +0.59% 35.67 kB 35.88 kB
oss-experimental/react-server-dom-esm/cjs/react-server-dom-esm-client.browser.development.js +0.43% 114.38 kB 114.87 kB +0.49% 21.51 kB 21.61 kB
oss-experimental/react-server-dom-parcel/cjs/react-server-dom-parcel-client.browser.development.js +0.43% 114.73 kB 115.23 kB +0.48% 21.45 kB 21.55 kB
oss-experimental/react-client/cjs/react-client-flight.development.js +0.43% 116.03 kB 116.52 kB +0.48% 21.26 kB 21.36 kB
oss-experimental/react-server-dom-turbopack/cjs/react-server-dom-turbopack-client.browser.development.js +0.42% 116.56 kB 117.06 kB +0.47% 21.90 kB 22.01 kB
oss-experimental/react-server-dom-webpack/cjs/react-server-dom-webpack-client.browser.development.js +0.42% 117.12 kB 117.61 kB +0.51% 22.04 kB 22.15 kB
oss-experimental/react-server-dom-parcel/cjs/react-server-dom-parcel-client.node.development.js +0.42% 118.46 kB 118.96 kB +0.50% 22.27 kB 22.38 kB
oss-experimental/react-server-dom-esm/cjs/react-server-dom-esm-client.node.development.js +0.42% 118.97 kB 119.47 kB +0.49% 22.30 kB 22.41 kB
oss-experimental/react-server-dom-parcel/cjs/react-server-dom-parcel-client.edge.development.js +0.41% 120.26 kB 120.76 kB +0.49% 22.64 kB 22.75 kB
oss-experimental/react-server-dom-turbopack/cjs/react-server-dom-turbopack-client.node.development.js +0.41% 121.61 kB 122.11 kB +0.48% 22.78 kB 22.89 kB
oss-experimental/react-server-dom-turbopack/cjs/react-server-dom-turbopack-client.edge.development.js +0.40% 123.36 kB 123.85 kB +0.48% 23.14 kB 23.25 kB
oss-experimental/react-server-dom-webpack/cjs/react-server-dom-webpack-client.edge.development.js +0.40% 123.45 kB 123.94 kB +0.48% 23.18 kB 23.29 kB
oss-experimental/react-server-dom-webpack/cjs/react-server-dom-webpack-client.node.unbundled.development.js +0.40% 124.36 kB 124.85 kB +0.52% 23.07 kB 23.19 kB
oss-experimental/react-server-dom-webpack/cjs/react-server-dom-webpack-client.node.development.js +0.39% 125.69 kB 126.18 kB +0.51% 23.32 kB 23.44 kB

Generated by 🚫 dangerJS against 3e749b9

@sebmarkbage sebmarkbage force-pushed the ioawaitthenable branch 2 times, most recently from ca2f907 to 6e4b5b8 Compare June 3, 2025 04:25
}

async function Component() {
const data = await fetchThirdParty(ThirdPartyComponent);
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 is the essence of this feature.

This turns into a cycle but it's fine as long as we just ignore it since it
doesn't depend on I/O.
One thing that's a bit confusing is that an UnresolvedAwaitNode can resolve
after its children since it's resolved after the then() function is invoked.
… it resolves

Since this expando can't be assigned before it has been constructed, we can't
do it in init. Unfortunately, we don't get passed the instance in promiseResolve()
so we need to retain the instance.

We must use a WeakRef since otherwise an infinite Promise that never resolves
can't be GC:ed.

If it does resolve, then it must be retained by the runtime until it finishes
resolving at least. After that it might be too late so we have to then
extract the information.

A possible alternative strategy would be to install a setter on the Promise.
We do this whether this was blocked by I/O or not. This lets you create
virtual nodes that behave like I/O.
That ensures that work spawned from the resulting render isn't included
in the sequence. This helps isolate one render from another.

TODO: We should probably mark startFlowing and abort too.
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