Skip to content

[Flight] Encode Async I/O Tasks using the Enclosing Line/Column #33403

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 1 commit into from
Jun 3, 2025

Conversation

sebmarkbage
Copy link
Collaborator

Stacked on #33402.

There's a bug in Chrome Performance tracking which uses the enclosing line/column instead of the callsite in stacks.

For our fake eval:ed functions that represents functions on the server, we can position the enclosing function body at the position of the callsite to simulate getting the right line.

Unfortunately, that doesn't give us exactly the right callsite when it's used for other purposes that uses the callsite like console logs and error reporting and stacks inside breakpoints. So I don't think we want to always do this.

For ReactAsyncInfo/ReactIOInfo, the only thing we're going to use the fake task for is the Performance tracking, so it doesn't have any downsides until Chrome fixes the bug and we'd have to revert it. Therefore this PR uses that techniques only for those entries.

We could do this for Server Components too but we're going to use those for other things too like console logs. I don't think it's worth duplicating the Task objects. That would also make it inconsistent with Client Components.

For Client Components, we could in theory also generate fake evals but that would be way slower since there's so many of them and currently we rely on the native implementation for those. So doesn't seem worth fixing.

But since we can at least fix it for RSC I/O/awaits we can do this hack.

@sebmarkbage sebmarkbage requested a review from eps1lon June 2, 2025 04:09
@github-actions github-actions bot added the React Core Team Opened by a member of the React Core Team label Jun 2, 2025
@react-sizebot
Copy link

react-sizebot commented Jun 2, 2025

Comparing: 9cc74fe...c9ef691

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.63 kB 114.63 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-stable-semver/react-server-dom-esm/esm/react-server-dom-esm-client.browser.development.js +0.59% 136.37 kB 137.17 kB +0.85% 31.93 kB 32.20 kB
oss-stable/react-server-dom-esm/esm/react-server-dom-esm-client.browser.development.js +0.59% 136.40 kB 137.20 kB +0.84% 31.95 kB 32.22 kB
oss-stable-semver/react-server-dom-esm/cjs/react-server-dom-esm-client.browser.development.js +0.58% 98.57 kB 99.14 kB +0.43% 18.68 kB 18.76 kB
oss-stable/react-server-dom-esm/cjs/react-server-dom-esm-client.browser.development.js +0.58% 98.62 kB 99.19 kB +0.43% 18.71 kB 18.79 kB
oss-stable-semver/react-server-dom-parcel/cjs/react-server-dom-parcel-client.browser.development.js +0.58% 98.93 kB 99.50 kB +0.46% 18.64 kB 18.73 kB
oss-stable/react-server-dom-parcel/cjs/react-server-dom-parcel-client.browser.development.js +0.57% 98.98 kB 99.55 kB +0.46% 18.67 kB 18.75 kB
oss-stable-semver/react-client/cjs/react-client-flight.development.js +0.57% 100.25 kB 100.82 kB +0.46% 18.44 kB 18.53 kB
oss-stable/react-client/cjs/react-client-flight.development.js +0.57% 100.28 kB 100.85 kB +0.46% 18.46 kB 18.55 kB
oss-stable-semver/react-server-dom-turbopack/cjs/react-server-dom-turbopack-client.browser.development.js +0.56% 100.76 kB 101.33 kB +0.42% 19.08 kB 19.16 kB
oss-stable/react-server-dom-turbopack/cjs/react-server-dom-turbopack-client.browser.development.js +0.56% 100.81 kB 101.38 kB +0.41% 19.10 kB 19.18 kB
oss-stable-semver/react-server-dom-webpack/cjs/react-server-dom-webpack-client.browser.development.js +0.56% 101.32 kB 101.89 kB +0.42% 19.21 kB 19.29 kB
oss-stable/react-server-dom-webpack/cjs/react-server-dom-webpack-client.browser.development.js +0.56% 101.37 kB 101.94 kB +0.42% 19.24 kB 19.32 kB
oss-stable-semver/react-server-dom-parcel/cjs/react-server-dom-parcel-client.node.development.js +0.55% 102.72 kB 103.29 kB +0.44% 19.46 kB 19.55 kB
oss-stable/react-server-dom-parcel/cjs/react-server-dom-parcel-client.node.development.js +0.55% 102.72 kB 103.29 kB +0.44% 19.46 kB 19.55 kB
oss-stable-semver/react-server-dom-esm/cjs/react-server-dom-esm-client.node.development.js +0.55% 103.23 kB 103.80 kB +0.45% 19.47 kB 19.55 kB
oss-stable/react-server-dom-esm/cjs/react-server-dom-esm-client.node.development.js +0.55% 103.23 kB 103.80 kB +0.45% 19.47 kB 19.55 kB
oss-stable-semver/react-server-dom-parcel/cjs/react-server-dom-parcel-client.edge.development.js +0.54% 104.52 kB 105.09 kB +0.44% 19.78 kB 19.87 kB
oss-stable/react-server-dom-parcel/cjs/react-server-dom-parcel-client.edge.development.js +0.54% 104.52 kB 105.09 kB +0.44% 19.78 kB 19.87 kB
oss-stable-semver/react-server-dom-turbopack/cjs/react-server-dom-turbopack-client.node.development.js +0.54% 105.87 kB 106.44 kB +0.44% 19.95 kB 20.04 kB
oss-stable/react-server-dom-turbopack/cjs/react-server-dom-turbopack-client.node.development.js +0.54% 105.87 kB 106.44 kB +0.44% 19.95 kB 20.04 kB
oss-experimental/react-server-dom-esm/esm/react-server-dom-esm-client.browser.development.js +0.53% 152.27 kB 153.08 kB +0.77% 35.39 kB 35.67 kB
oss-stable-semver/react-server-dom-turbopack/cjs/react-server-dom-turbopack-client.edge.development.js +0.53% 107.62 kB 108.19 kB +0.40% 20.27 kB 20.35 kB
oss-stable/react-server-dom-turbopack/cjs/react-server-dom-turbopack-client.edge.development.js +0.53% 107.62 kB 108.19 kB +0.40% 20.27 kB 20.35 kB
oss-stable-semver/react-server-dom-webpack/cjs/react-server-dom-webpack-client.edge.development.js +0.53% 107.71 kB 108.28 kB +0.40% 20.30 kB 20.39 kB
oss-stable/react-server-dom-webpack/cjs/react-server-dom-webpack-client.edge.development.js +0.53% 107.71 kB 108.28 kB +0.40% 20.30 kB 20.39 kB
oss-stable-semver/react-server-dom-webpack/cjs/react-server-dom-webpack-client.node.unbundled.development.js +0.52% 108.61 kB 109.18 kB +0.44% 20.22 kB 20.31 kB
oss-stable/react-server-dom-webpack/cjs/react-server-dom-webpack-client.node.unbundled.development.js +0.52% 108.61 kB 109.18 kB +0.44% 20.22 kB 20.31 kB
oss-stable-semver/react-server-dom-webpack/cjs/react-server-dom-webpack-client.node.development.js +0.52% 109.95 kB 110.52 kB +0.43% 20.47 kB 20.56 kB
oss-stable/react-server-dom-webpack/cjs/react-server-dom-webpack-client.node.development.js +0.52% 109.95 kB 110.52 kB +0.43% 20.47 kB 20.56 kB
oss-experimental/react-server-dom-esm/cjs/react-server-dom-esm-client.browser.development.js +0.51% 113.80 kB 114.38 kB +0.38% 21.42 kB 21.51 kB
oss-experimental/react-server-dom-parcel/cjs/react-server-dom-parcel-client.browser.development.js +0.51% 114.15 kB 114.73 kB +0.38% 21.36 kB 21.45 kB
oss-experimental/react-client/cjs/react-client-flight.development.js +0.50% 115.45 kB 116.03 kB +0.39% 21.18 kB 21.26 kB
oss-experimental/react-server-dom-turbopack/cjs/react-server-dom-turbopack-client.browser.development.js +0.50% 115.98 kB 116.56 kB +0.38% 21.82 kB 21.90 kB
oss-experimental/react-server-dom-webpack/cjs/react-server-dom-webpack-client.browser.development.js +0.50% 116.54 kB 117.12 kB +0.38% 21.95 kB 22.04 kB
oss-experimental/react-server-dom-parcel/cjs/react-server-dom-parcel-client.node.development.js +0.49% 117.88 kB 118.46 kB +0.42% 22.18 kB 22.27 kB
oss-experimental/react-server-dom-esm/cjs/react-server-dom-esm-client.node.development.js +0.49% 118.39 kB 118.97 kB +0.40% 22.22 kB 22.30 kB
oss-experimental/react-server-dom-parcel/cjs/react-server-dom-parcel-client.edge.development.js +0.49% 119.68 kB 120.26 kB +0.39% 22.56 kB 22.64 kB
oss-experimental/react-server-dom-turbopack/cjs/react-server-dom-turbopack-client.node.development.js +0.48% 121.03 kB 121.61 kB +0.39% 22.69 kB 22.78 kB
oss-experimental/react-server-dom-turbopack/cjs/react-server-dom-turbopack-client.edge.development.js +0.47% 122.78 kB 123.36 kB +0.36% 23.06 kB 23.14 kB
oss-experimental/react-server-dom-webpack/cjs/react-server-dom-webpack-client.edge.development.js +0.47% 122.87 kB 123.45 kB +0.36% 23.09 kB 23.18 kB
oss-experimental/react-server-dom-webpack/cjs/react-server-dom-webpack-client.node.unbundled.development.js +0.47% 123.77 kB 124.36 kB +0.39% 22.98 kB 23.07 kB
oss-experimental/react-server-dom-webpack/cjs/react-server-dom-webpack-client.node.development.js +0.46% 125.11 kB 125.69 kB +0.38% 23.23 kB 23.32 kB

Generated by 🚫 dangerJS against c9ef691

@VIGDXX
Copy link

VIGDXX commented Jun 3, 2025

@CodiumAI-Agent /improve

@CodiumAI-Agent
Copy link

PR Code Suggestions ✨

CategorySuggestion                                                                                                                                    Impact
General
Explicitly detect debugInfo type

Rather than relying on debugInfo.key, explicitly detect async and I/O info by
checking for the presence of their characteristic fields (e.g. awaited or start).
This prevents accidentally enabling enclosing-line for all component debug infos.

packages/react-client/src/ReactFlightClient.js [2493]

-const useEnclosingLine = debugInfo.key === undefined;
+const useEnclosingLine =
+  typeof debugInfo.awaited !== 'undefined' || typeof debugInfo.start === 'number';
Suggestion importance[1-10]: 3

__

Why: Switching from a key check to field presence is a valid style choice but only offers a minor clarity benefit and isn’t required for correctness.

Low

@VIGDXX
Copy link

VIGDXX commented Jun 3, 2025

@CodiumAI-Agent /describe

@CodiumAI-Agent
Copy link

Title

[Flight] Encode Async I/O Tasks using the Enclosing Line/Column


User description

Stacked on #33402.

There's a bug in Chrome Performance tracking which uses the enclosing line/column instead of the callsite in stacks.

For our fake eval:ed functions that represents functions on the server, we can position the enclosing function body at the position of the callsite to simulate getting the right line.

Unfortunately, that doesn't give us exactly the right callsite when it's used for other purposes that uses the callsite like console logs and error reporting and stacks inside breakpoints. So I don't think we want to always do this.

For ReactAsyncInfo/ReactIOInfo, the only thing we're going to use the fake task for is the Performance tracking, so it doesn't have any downsides until Chrome fixes the bug and we'd have to revert it. Therefore this PR uses that techniques only for those entries.

We could do this for Server Components too but we're going to use those for other things too like console logs. I don't think it's worth duplicating the Task objects. That would also make it inconsistent with Client Components.

For Client Components, we could in theory also generate fake evals but that would be way slower since there's so many of them and currently we rely on the native implementation for those. So doesn't seem worth fixing.

But since we can at least fix it for RSC I/O/awaits we can do this hack.


PR Type

Enhancement, Tests


Description

  • Instrument Flight client/server with async I/O tracking

  • Introduce ReactIOInfo and AsyncSequence types

  • Hook Node async_hooks for Promise/await sequences

  • Add tests for async debug info and snapshots


Changes walkthrough 📝

Relevant files
Enhancement
8 files
ReactFlightClient.js
Add ReactIOInfo and async debug hooks                                       
+159/-26
ReactFlightPerformanceTrack.js
Log I/O and await events in UserTiming                                     
+113/-3 
ReactFlightAsyncSequence.js
Define IONode, PromiseNode, AwaitNode sequence types         
+46/-0   
ReactFlightServer.js
Integrate async debug info and resolveIOInfo                         
+436/-52
ReactFlightServerConfigDebugNode.js
Instrument async_hooks for sequence tracking                         
+126/-6 
ReactFlightServerConfigDebugNoop.js
Provide noop async debug info stub                                             
+5/-0     
ReactFlightStackConfigV8.js
Cache parsed stack traces for reuse                                           
+14/-0   
ReactTypes.js
Add ReactIOInfo and update ReactAsyncInfo types                   
+17/-2   
Tests
2 files
ReactFlightAsyncDebugInfo-test.js
Add tests for async debug info snapshots                                 
+469/-0 
setupTests.js
Mock and clean up async_hooks in tests                                     
+15/-0   
Configuration changes
8 files
inlinedHostConfigs.js
Include DebugNoop in host config paths                                     
+32/-8   
ReactFlightServerConfig.dom-edge-parcel.js
Use DebugNoop instead of DebugNode in edge parcel               
+1/-15   
ReactFlightServerConfig.dom-edge-turbopack.js
Use DebugNoop stub in edge turbopack config                           
+1/-15   
ReactFlightServerConfig.dom-edge.js
Use DebugNoop for DOM edge config                                               
+1/-15   
ReactFlightServerConfig.dom-node-esm.js
Remove async_hooks import for ESM build                                   
+0/-2     
ReactFlightServerConfig.dom-node-parcel.js
Remove async_hooks import for parcel Node build                   
+0/-2     
ReactFlightServerConfig.dom-node-turbopack.js
Remove async_hooks import for turbopack Node build             
+0/-2     
ReactFlightServerConfig.dom-node.js
Remove async_hooks import in Node config                                 
+0/-2     

Need help?
  • Type /help how to ... in the comments thread for any questions about PR-Agent usage.
  • Check out the documentation for more information.
  • @VIGDXX
    Copy link

    VIGDXX commented Jun 3, 2025

    @CodiumAI-Agent /review

    @sebmarkbage sebmarkbage force-pushed the ioenclosinglinecol branch from 020ce0f to c9ef691 Compare June 3, 2025 21:30
    @sebmarkbage sebmarkbage merged commit 1540081 into facebook:main Jun 3, 2025
    14 of 16 checks passed
    sebmarkbage added a commit that referenced this pull request Jun 4, 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.
    Comment on lines +2439 to +2440
    useEnclosingLine ? line : enclosingLine,
    useEnclosingLine ? col : enclosingCol,
    Copy link
    Collaborator

    Choose a reason for hiding this comment

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

    Looking at these two lines in isolation, it seems that the condition should be flipped. Am I missing context, or is this wrong?

    Copy link
    Collaborator

    Choose a reason for hiding this comment

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

    I think I get it now, if useEnclosingLine/Col is true, we set the line/col also as enclosing line/col, so that it's not off when Chrome picks the enclosing line/col. I guess the name of the boolean variable confused me a bit.

    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.

    7 participants