Skip to content

Conversation

@sebmarkbage
Copy link
Collaborator

@sebmarkbage sebmarkbage commented Jul 8, 2025

We need to optimize the collection of debug info for dev mode. This is an incredibly hot path since it instruments all I/O and Promises in the app.

These optimizations focus primarily on the collection of stack traces. They are expensive to collect because we need to eagerly collect the stacks since they can otherwise cause memory leaks. We also need to do some of the processing of them up front. We also end up only using a few of them in the end but we don't know which ones we'll use.

The first compromise here is that I now only collect the stacks of "awaits" if they were in a specific request's render. In some cases it's useful to collect them even outside of this if they're part of a sequence that started early. I still collect stacks for the created Promises outside of this though which can still provide some context.

The other optimization to awaits, is that since we'll only use the inner most one that had an await directly in userspace, we can stop collecting stacks on a chain of awaits after we find one. This requires a quick filter on a single callsite to determine. Since we now only collect stacks from awaits that belongs to a specific Request we can use that request's specific filter option. Technically this might not be quite correct if that same thing ends up deduped across Requests but that's an edge case.

Additionally, I now stop collecting stack for I/O nodes. They're almost always superseded by the Promise that wraps them anyway. Even if you write mostly Promise free code, you'll likely end up with a Promise at the root of the component eventually anyway and then you end up using its stack anyway. You have to really contort the code to end up with zero Promises at which point it's not very useful anyway. At best it's maybe mostly useful for giving a name to the I/O when the rest is just stuff like new Promise.

However, a possible alternative optimization could be to only collect the stack of spawned I/O and not the stack of Promises. The issue with Promises (not awaits) is that we never know what will end up resolving them in the end when they're created so we have to always eagerly collect stacks. This could be an issue when you have a lot of abstractions that end up not actually be related to I/O at all. The issue with collecting stacks only for I/O is that the actual I/O can be pooled or batched so you end up not having the stack when the conceptual start of each operation within the batch started. Which is why I decided to keep the Promise stack.

…ite on a chain of awaits

This requires eagerly applying a filter to single await frame. For a lot
of internal awaits this still needs to collect a lot but once we've found
one we can stop including stacks.
@sebmarkbage sebmarkbage requested a review from eps1lon July 8, 2025 03:41
@github-actions github-actions bot added the React Core Team Opened by a member of the React Core Team label Jul 8, 2025
@react-sizebot
Copy link

react-sizebot commented Jul 8, 2025

Comparing: e4314a0...f2feaa6

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.50 kB 530.50 kB = 93.66 kB 93.66 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 = 655.04 kB 655.04 kB = 115.35 kB 115.35 kB
facebook-www/ReactDOM-prod.classic.js = 674.93 kB 674.93 kB = 118.71 kB 118.71 kB
facebook-www/ReactDOM-prod.modern.js = 665.36 kB 665.36 kB = 117.06 kB 117.06 kB

Significant size changes

Includes any change greater than 0.2%:

Expand to show
Name +/- Base Current +/- gzip Base gzip Current gzip
oss-stable-semver/react-server/cjs/react-server-flight.production.js +1.44% 60.61 kB 61.48 kB +1.99% 12.15 kB 12.39 kB
oss-stable/react-server/cjs/react-server-flight.production.js +1.44% 60.61 kB 61.48 kB +1.99% 12.15 kB 12.39 kB
oss-experimental/react-server/cjs/react-server-flight.production.js +1.32% 66.22 kB 67.10 kB +1.89% 13.03 kB 13.28 kB
oss-stable-semver/react-server/cjs/react-server-flight.development.js +0.44% 125.57 kB 126.12 kB +0.71% 22.81 kB 22.97 kB
oss-stable/react-server/cjs/react-server-flight.development.js +0.44% 125.57 kB 126.12 kB +0.71% 22.81 kB 22.97 kB
oss-experimental/react-server/cjs/react-server-flight.development.js +0.41% 134.16 kB 134.71 kB +0.68% 24.34 kB 24.51 kB
oss-experimental/react-server-dom-esm/cjs/react-server-dom-esm-server.node.development.js +0.41% 195.34 kB 196.14 kB +0.17% 36.12 kB 36.18 kB
oss-experimental/react-server-dom-parcel/cjs/react-server-dom-parcel-server.node.development.js +0.40% 201.31 kB 202.11 kB +0.23% 36.79 kB 36.88 kB
oss-experimental/react-server-dom-webpack/cjs/react-server-dom-webpack-server.node.unbundled.development.js +0.38% 208.08 kB 208.88 kB +0.35% 37.88 kB 38.01 kB
oss-experimental/react-server-dom-turbopack/cjs/react-server-dom-turbopack-server.node.development.js +0.38% 209.23 kB 210.03 kB +0.37% 38.18 kB 38.32 kB
oss-experimental/react-server-dom-webpack/cjs/react-server-dom-webpack-server.node.development.js +0.38% 209.29 kB 210.08 kB +0.34% 38.20 kB 38.33 kB

Generated by 🚫 dangerJS against f2feaa6

In typical usage we almost always will end up using the stack of the Promise
instead if one exists. It's only useful in fairly contrived scenarios where
you don't use Promises but even if you don't use Promises in your code
then you'll likely end up with a Promise at the root of the component which
ends up winning in the end anyway.
@sebmarkbage sebmarkbage force-pushed the optimizeasyncdebug branch from 5d69c66 to f2feaa6 Compare July 8, 2025 03:54
@sebmarkbage sebmarkbage merged commit f1ecf82 into facebook:main Jul 8, 2025
241 checks passed
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.

5 participants