From c7c7306136dbd5839769cf67ccdef476fae19c5c Mon Sep 17 00:00:00 2001 From: Sebastian Markbage Date: Sun, 8 Jun 2025 22:09:41 -0400 Subject: [PATCH 01/11] Extract an AsyncSequence synchronously from a Promise 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. --- .../src/ReactFlightServerConfigDebugNode.js | 31 ++++++++++++++++++- .../src/ReactFlightServerConfigDebugNoop.js | 5 +++ scripts/flow/environment.js | 4 ++- 3 files changed, 38 insertions(+), 2 deletions(-) diff --git a/packages/react-server/src/ReactFlightServerConfigDebugNode.js b/packages/react-server/src/ReactFlightServerConfigDebugNode.js index ddcf256525b6d..46a29fb3cb2ef 100644 --- a/packages/react-server/src/ReactFlightServerConfigDebugNode.js +++ b/packages/react-server/src/ReactFlightServerConfigDebugNode.js @@ -24,9 +24,12 @@ import { UNRESOLVED_AWAIT_NODE, } from './ReactFlightAsyncSequence'; import {resolveOwner} from './flight/ReactFlightCurrentOwner'; -import {createHook, executionAsyncId} from 'async_hooks'; +import {createHook, executionAsyncId, AsyncResource} from 'async_hooks'; import {enableAsyncDebugInfo} from 'shared/ReactFeatureFlags'; +// $FlowFixMe[method-unbinding] +const getAsyncId = AsyncResource.prototype.asyncId; + const pendingOperations: Map = __DEV__ && enableAsyncDebugInfo ? new Map() : (null: any); @@ -260,3 +263,29 @@ export function getCurrentAsyncSequence(): null | AsyncSequence { } return currentNode; } + +export function getAsyncSequenceFromPromise( + promise: any, +): null | AsyncSequence { + if (!__DEV__ || !enableAsyncDebugInfo) { + return null; + } + // A Promise is conceptually an AsyncResource but doesn't have its own methods. + // We use this hack to extract the internal asyncId off the Promise. + let asyncId: void | number; + try { + asyncId = getAsyncId.call(promise); + } catch (x) { + // Ignore errors extracting the ID. We treat it as missing. + // This could happen if our hack stops working or in the case where this is + // a Proxy that throws such as our own ClientReference proxies. + } + if (asyncId === undefined) { + return null; + } + const node = pendingOperations.get(asyncId); + if (node === undefined) { + return null; + } + return node; +} diff --git a/packages/react-server/src/ReactFlightServerConfigDebugNoop.js b/packages/react-server/src/ReactFlightServerConfigDebugNoop.js index 7418aaef18310..e435929114b57 100644 --- a/packages/react-server/src/ReactFlightServerConfigDebugNoop.js +++ b/packages/react-server/src/ReactFlightServerConfigDebugNoop.js @@ -15,3 +15,8 @@ export function markAsyncSequenceRootTask(): void {} export function getCurrentAsyncSequence(): null | AsyncSequence { return null; } +export function getAsyncSequenceFromPromise( + promise: any, +): null | AsyncSequence { + return null; +} diff --git a/scripts/flow/environment.js b/scripts/flow/environment.js index d66ef65d9d318..39c792b449277 100644 --- a/scripts/flow/environment.js +++ b/scripts/flow/environment.js @@ -356,7 +356,9 @@ declare module 'async_hooks' { run(store: T, callback: (...args: any[]) => R, ...args: any[]): R; enterWith(store: T): void; } - declare interface AsyncResource {} + declare class AsyncResource { + asyncId(): number; + } declare function executionAsyncId(): number; declare function executionAsyncResource(): AsyncResource; declare function triggerAsyncId(): number; From b1088de6aed8e56fd2b6105a00303818172646ef Mon Sep 17 00:00:00 2001 From: Sebastian Markbage Date: Sun, 8 Jun 2025 22:10:40 -0400 Subject: [PATCH 02/11] Only get the async sequence from rendering native Promises --- .../react-server/src/ReactFlightServer.js | 88 +++++++++++-------- 1 file changed, 53 insertions(+), 35 deletions(-) diff --git a/packages/react-server/src/ReactFlightServer.js b/packages/react-server/src/ReactFlightServer.js index 3a56148f7101d..3a9955c465981 100644 --- a/packages/react-server/src/ReactFlightServer.js +++ b/packages/react-server/src/ReactFlightServer.js @@ -91,6 +91,7 @@ import { initAsyncDebugInfo, markAsyncSequenceRootTask, getCurrentAsyncSequence, + getAsyncSequenceFromPromise, parseStackTrace, supportsComponentStorage, componentStorage, @@ -690,26 +691,14 @@ function serializeThenable( switch (thenable.status) { case 'fulfilled': { - if (__DEV__) { - // If this came from Flight, forward any debug info into this new row. - const debugInfo: ?ReactDebugInfo = (thenable: any)._debugInfo; - if (debugInfo) { - forwardDebugInfo(request, newTask, debugInfo); - } - } + forwardDebugInfoFromThenable(request, newTask, thenable); // We have the resolved value, we can go ahead and schedule it for serialization. newTask.model = thenable.value; pingTask(request, newTask); return newTask.id; } case 'rejected': { - if (__DEV__) { - // If this came from Flight, forward any debug info into this new row. - const debugInfo: ?ReactDebugInfo = (thenable: any)._debugInfo; - if (debugInfo) { - forwardDebugInfo(request, newTask, debugInfo); - } - } + forwardDebugInfoFromThenable(request, newTask, thenable); const x = thenable.reason; erroredTask(request, newTask, x); return newTask.id; @@ -758,24 +747,11 @@ function serializeThenable( thenable.then( value => { - if (__DEV__) { - // If this came from Flight, forward any debug info into this new row. - const debugInfo: ?ReactDebugInfo = (thenable: any)._debugInfo; - if (debugInfo) { - forwardDebugInfo(request, newTask, debugInfo); - } - } + forwardDebugInfoFromCurrentContext(request, newTask, thenable); newTask.model = value; pingTask(request, newTask); }, reason => { - if (__DEV__) { - // If this came from Flight, forward any debug info into this new row. - const debugInfo: ?ReactDebugInfo = (thenable: any)._debugInfo; - if (debugInfo) { - forwardDebugInfo(request, newTask, debugInfo); - } - } if (newTask.status === PENDING) { if (enableProfilerTimer && enableComponentPerformanceTrack) { // If this is async we need to time when this task finishes. @@ -1098,7 +1074,7 @@ function createLazyWrapperAroundWakeable(wakeable: Wakeable) { }; if (__DEV__) { // If this came from React, transfer the debug info. - lazyType._debugInfo = (thenable: any)._debugInfo || []; + lazyType._debugInfo = thenable._debugInfo || []; } return lazyType; } @@ -2044,12 +2020,6 @@ function pingTask(request: Request, task: Task): void { if (enableProfilerTimer && enableComponentPerformanceTrack) { // If this was async we need to emit the time when it completes. task.timed = true; - if (enableAsyncDebugInfo) { - const sequence = getCurrentAsyncSequence(); - if (sequence !== null) { - emitAsyncSequence(request, task, sequence); - } - } } const pingedTasks = request.pingedTasks; pingedTasks.push(task); @@ -4316,6 +4286,54 @@ function forwardDebugInfo( } } +function forwardDebugInfoFromThenable( + request: Request, + task: Task, + thenable: Thenable, +): void { + if (__DEV__) { + // If this came from Flight, forward any debug info into this new row. + const debugInfo: ?ReactDebugInfo = thenable._debugInfo; + if (debugInfo) { + forwardDebugInfo(request, task, debugInfo); + } + } + if ( + enableProfilerTimer && + enableComponentPerformanceTrack && + enableAsyncDebugInfo + ) { + const sequence = getAsyncSequenceFromPromise(thenable); + if (sequence !== null) { + emitAsyncSequence(request, task, sequence); + } + } +} + +function forwardDebugInfoFromCurrentContext( + request: Request, + task: Task, + thenable: Thenable, +): void { + if (__DEV__) { + // If this came from Flight, forward any debug info into this new row. + const debugInfo: ?ReactDebugInfo = thenable._debugInfo; + if (debugInfo) { + forwardDebugInfo(request, task, debugInfo); + } + } + if ( + enableProfilerTimer && + enableComponentPerformanceTrack && + enableAsyncDebugInfo + ) { + const sequence = getCurrentAsyncSequence(); + if (sequence !== null) { + emitAsyncSequence(request, task, sequence); + } + } +} + function emitTimingChunk( request: Request, id: number, From aa5f531740524989db8ef69ad8c7dc8c9c847fe1 Mon Sep 17 00:00:00 2001 From: Sebastian Markbage Date: Sun, 8 Jun 2025 23:04:12 -0400 Subject: [PATCH 03/11] Forwards debug info from Promises used in use() --- .../src/__tests__/ReactFlight-test.js | 58 +++++++++++++++++++ packages/react-server/src/ReactFlightHooks.js | 6 ++ .../react-server/src/ReactFlightServer.js | 17 ++++++ .../react-server/src/ReactFlightThenable.js | 2 +- 4 files changed, 82 insertions(+), 1 deletion(-) diff --git a/packages/react-client/src/__tests__/ReactFlight-test.js b/packages/react-client/src/__tests__/ReactFlight-test.js index eb354aba58753..49968b33590d9 100644 --- a/packages/react-client/src/__tests__/ReactFlight-test.js +++ b/packages/react-client/src/__tests__/ReactFlight-test.js @@ -2991,6 +2991,64 @@ describe('ReactFlight', () => { ); }); + // @gate !__DEV__ || enableComponentPerformanceTrack + it('preserves debug info for server-to-server through use()', async () => { + function ThirdPartyComponent() { + return 'hi'; + } + + function ServerComponent({transport}) { + // This is a Server Component that receives other Server Components from a third party. + const text = ReactServer.use(ReactNoopFlightClient.read(transport)); + return
{text.toUpperCase()}
; + } + + const thirdPartyTransport = ReactNoopFlightServer.render( + , + { + environmentName: 'third-party', + }, + ); + + const transport = ReactNoopFlightServer.render( + , + ); + + await act(async () => { + const promise = ReactNoopFlightClient.read(transport); + expect(getDebugInfo(promise)).toEqual( + __DEV__ + ? [ + {time: 16}, + { + name: 'ServerComponent', + env: 'Server', + key: null, + stack: ' in Object. (at **)', + props: { + transport: expect.arrayContaining([]), + }, + }, + {time: 16}, + { + name: 'ThirdPartyComponent', + env: 'third-party', + key: null, + stack: ' in Object. (at **)', + props: {}, + }, + {time: 16}, + {time: 17}, + ] + : undefined, + ); + const result = await promise; + ReactNoop.render(result); + }); + + expect(ReactNoop).toMatchRenderedOutput(
HI
); + }); + it('preserves error stacks passed through server-to-server with source maps', async () => { async function ServerComponent({transport}) { // This is a Server Component that receives other Server Components from a third party. diff --git a/packages/react-server/src/ReactFlightHooks.js b/packages/react-server/src/ReactFlightHooks.js index c5ffc5dd70342..ed369be0e9b18 100644 --- a/packages/react-server/src/ReactFlightHooks.js +++ b/packages/react-server/src/ReactFlightHooks.js @@ -58,6 +58,12 @@ export function getThenableStateAfterSuspending(): ThenableState { return state; } +export function getTrackedThenablesAfterRendering(): null | Array< + Thenable, +> { + return thenableState; +} + export const HooksDispatcher: Dispatcher = { readContext: (unsupportedContext: any), diff --git a/packages/react-server/src/ReactFlightServer.js b/packages/react-server/src/ReactFlightServer.js index 3a9955c465981..34056987d5196 100644 --- a/packages/react-server/src/ReactFlightServer.js +++ b/packages/react-server/src/ReactFlightServer.js @@ -107,6 +107,7 @@ import { prepareToUseHooksForRequest, prepareToUseHooksForComponent, getThenableStateAfterSuspending, + getTrackedThenablesAfterRendering, resetHooksForRequest, } from './ReactFlightHooks'; import {DefaultAsyncDispatcher} from './flight/ReactFlightAsyncDispatcher'; @@ -1384,6 +1385,22 @@ function renderFunctionComponent( throw null; } + if ( + __DEV__ || + (enableProfilerTimer && + enableComponentPerformanceTrack && + enableAsyncDebugInfo) + ) { + // Forward any debug information for any Promises that we use():ed during the render. + // We do this at the end so that we don't keep doing this for each retry. + const trackedThenables = getTrackedThenablesAfterRendering(); + if (trackedThenables !== null) { + for (let i = 0; i < trackedThenables.length; i++) { + forwardDebugInfoFromThenable(request, task, trackedThenables[i]); + } + } + } + // Apply special cases. result = processServerComponentReturnValue(request, task, Component, result); diff --git a/packages/react-server/src/ReactFlightThenable.js b/packages/react-server/src/ReactFlightThenable.js index 47e7b914da176..87fc2ea1fbfde 100644 --- a/packages/react-server/src/ReactFlightThenable.js +++ b/packages/react-server/src/ReactFlightThenable.js @@ -22,7 +22,7 @@ import type { import noop from 'shared/noop'; -export opaque type ThenableState = Array>; +export type ThenableState = Array>; // An error that is thrown (e.g. by `use`) to trigger Suspense. If we // detect this is caught by userspace, we'll log a warning in development. From d67eeb5b6b69a37d08ea0c33aa67fe76616071fa Mon Sep 17 00:00:00 2001 From: Sebastian Markbage Date: Sun, 8 Jun 2025 23:23:45 -0400 Subject: [PATCH 04/11] Special case _debugInfo property of proxies So might look for it on thenables when passed to use(). --- .../src/ReactFlightTurbopackReferences.js | 6 ++++++ .../src/ReactFlightWebpackReferences.js | 6 ++++++ .../src/ReactFlightServerTemporaryReferences.js | 3 +++ 3 files changed, 15 insertions(+) diff --git a/packages/react-server-dom-turbopack/src/ReactFlightTurbopackReferences.js b/packages/react-server-dom-turbopack/src/ReactFlightTurbopackReferences.js index 9d00b39efad25..baa297cf33d41 100644 --- a/packages/react-server-dom-turbopack/src/ReactFlightTurbopackReferences.js +++ b/packages/react-server-dom-turbopack/src/ReactFlightTurbopackReferences.js @@ -161,6 +161,9 @@ const deepProxyHandlers = { // reference. case 'defaultProps': return undefined; + // React looks for debugInfo on thenables. + case '_debugInfo': + return undefined; // Avoid this attempting to be serialized. case 'toJSON': return undefined; @@ -210,6 +213,9 @@ function getReference(target: Function, name: string | symbol): $FlowFixMe { // reference. case 'defaultProps': return undefined; + // React looks for debugInfo on thenables. + case '_debugInfo': + return undefined; // Avoid this attempting to be serialized. case 'toJSON': return undefined; diff --git a/packages/react-server-dom-webpack/src/ReactFlightWebpackReferences.js b/packages/react-server-dom-webpack/src/ReactFlightWebpackReferences.js index 60fe34b1c08df..c06e52a578ddb 100644 --- a/packages/react-server-dom-webpack/src/ReactFlightWebpackReferences.js +++ b/packages/react-server-dom-webpack/src/ReactFlightWebpackReferences.js @@ -162,6 +162,9 @@ const deepProxyHandlers = { // reference. case 'defaultProps': return undefined; + // React looks for debugInfo on thenables. + case '_debugInfo': + return undefined; // Avoid this attempting to be serialized. case 'toJSON': return undefined; @@ -211,6 +214,9 @@ function getReference(target: Function, name: string | symbol): $FlowFixMe { // reference. case 'defaultProps': return undefined; + // React looks for debugInfo on thenables. + case '_debugInfo': + return undefined; // Avoid this attempting to be serialized. case 'toJSON': return undefined; diff --git a/packages/react-server/src/ReactFlightServerTemporaryReferences.js b/packages/react-server/src/ReactFlightServerTemporaryReferences.js index 1f6b9f8ee3611..e368b2e800795 100644 --- a/packages/react-server/src/ReactFlightServerTemporaryReferences.js +++ b/packages/react-server/src/ReactFlightServerTemporaryReferences.js @@ -52,6 +52,9 @@ const proxyHandlers = { // reference. case 'defaultProps': return undefined; + // React looks for debugInfo on thenables. + case '_debugInfo': + return undefined; // Avoid this attempting to be serialized. case 'toJSON': return undefined; From 576e13ba688c58fab9b5cb2a016935dc6db7ebdb Mon Sep 17 00:00:00 2001 From: Sebastian Markbage Date: Sun, 8 Jun 2025 22:28:50 -0400 Subject: [PATCH 05/11] Forward debug info from a Promise returned from Server Component Before we wrap it in a Lazy since it won't be picked up by the lazy. --- .../react-server/src/ReactFlightServer.js | 23 +++++++++++-------- 1 file changed, 13 insertions(+), 10 deletions(-) diff --git a/packages/react-server/src/ReactFlightServer.js b/packages/react-server/src/ReactFlightServer.js index 34056987d5196..2cd060295fa15 100644 --- a/packages/react-server/src/ReactFlightServer.js +++ b/packages/react-server/src/ReactFlightServer.js @@ -1032,13 +1032,21 @@ function readThenable(thenable: Thenable): T { throw thenable; } -function createLazyWrapperAroundWakeable(wakeable: Wakeable) { +function createLazyWrapperAroundWakeable( + request: Request, + task: Task, + wakeable: Wakeable, +) { // This is a temporary fork of the `use` implementation until we accept // promises everywhere. const thenable: Thenable = (wakeable: any); switch (thenable.status) { - case 'fulfilled': + case 'fulfilled': { + forwardDebugInfoFromThenable(request, task, thenable); + return thenable.value; + } case 'rejected': + forwardDebugInfoFromThenable(request, task, thenable); break; default: { if (typeof thenable.status === 'string') { @@ -1051,6 +1059,7 @@ function createLazyWrapperAroundWakeable(wakeable: Wakeable) { pendingThenable.status = 'pending'; pendingThenable.then( fulfilledValue => { + forwardDebugInfoFromCurrentContext(request, task, thenable); if (thenable.status === 'pending') { const fulfilledThenable: FulfilledThenable = (thenable: any); fulfilledThenable.status = 'fulfilled'; @@ -1058,6 +1067,7 @@ function createLazyWrapperAroundWakeable(wakeable: Wakeable) { } }, (error: mixed) => { + forwardDebugInfoFromCurrentContext(request, task, thenable); if (thenable.status === 'pending') { const rejectedThenable: RejectedThenable = (thenable: any); rejectedThenable.status = 'rejected'; @@ -1073,10 +1083,6 @@ function createLazyWrapperAroundWakeable(wakeable: Wakeable) { _payload: thenable, _init: readThenable, }; - if (__DEV__) { - // If this came from React, transfer the debug info. - lazyType._debugInfo = thenable._debugInfo || []; - } return lazyType; } @@ -1155,12 +1161,9 @@ function processServerComponentReturnValue( } }, voidHandler); } - if (thenable.status === 'fulfilled') { - return thenable.value; - } // TODO: Once we accept Promises as children on the client, we can just return // the thenable here. - return createLazyWrapperAroundWakeable(result); + return createLazyWrapperAroundWakeable(request, task, result); } if (__DEV__) { From c11c984df6a2e986e45b39547f60d36721e7f075 Mon Sep 17 00:00:00 2001 From: Sebastian Markbage Date: Mon, 9 Jun 2025 00:19:13 -0400 Subject: [PATCH 06/11] Make sure we're not sending the same debugInfo twice If we're forwarding debugInfo and also visiting the underlying native Promise, make sure we're not emitting the same debugInfo twice. --- .../react-server/src/ReactFlightServer.js | 26 ++++++++++++------- 1 file changed, 17 insertions(+), 9 deletions(-) diff --git a/packages/react-server/src/ReactFlightServer.js b/packages/react-server/src/ReactFlightServer.js index 2cd060295fa15..a385fbb05b015 100644 --- a/packages/react-server/src/ReactFlightServer.js +++ b/packages/react-server/src/ReactFlightServer.js @@ -1880,7 +1880,7 @@ function visitAsyncNode( request: Request, task: Task, node: AsyncSequence, - visited: Set, + visited: Set, cutOff: number, ): null | PromiseNode | IONode { if (visited.has(node)) { @@ -1939,7 +1939,8 @@ function visitAsyncNode( // We need to forward after we visit awaited nodes because what ever I/O we requested that's // the thing that generated this node and its virtual children. const debugInfo = node.debugInfo; - if (debugInfo !== null) { + if (debugInfo !== null && !visited.has(debugInfo)) { + visited.add(debugInfo); forwardDebugInfo(request, task, debugInfo); } return match; @@ -1999,8 +2000,9 @@ function visitAsyncNode( } // We need to forward after we visit awaited nodes because what ever I/O we requested that's // the thing that generated this node and its virtual children. - const debugInfo: null | ReactDebugInfo = node.debugInfo; - if (debugInfo !== null) { + const debugInfo = node.debugInfo; + if (debugInfo !== null && !visited.has(debugInfo)) { + visited.add(debugInfo); forwardDebugInfo(request, task, debugInfo); } return match; @@ -2016,8 +2018,12 @@ function emitAsyncSequence( request: Request, task: Task, node: AsyncSequence, + alreadyForwardedDebugInfo: ?ReactDebugInfo, ): void { - const visited: Set = new Set(); + const visited: Set = new Set(); + if (__DEV__ && alreadyForwardedDebugInfo) { + visited.add(alreadyForwardedDebugInfo); + } const awaitedNode = visitAsyncNode(request, task, node, visited, task.time); if (awaitedNode !== null) { // Nothing in user space (unfiltered stack) awaited this. @@ -4311,9 +4317,10 @@ function forwardDebugInfoFromThenable( task: Task, thenable: Thenable, ): void { + let debugInfo: ?ReactDebugInfo; if (__DEV__) { // If this came from Flight, forward any debug info into this new row. - const debugInfo: ?ReactDebugInfo = thenable._debugInfo; + debugInfo = thenable._debugInfo; if (debugInfo) { forwardDebugInfo(request, task, debugInfo); } @@ -4325,7 +4332,7 @@ function forwardDebugInfoFromThenable( ) { const sequence = getAsyncSequenceFromPromise(thenable); if (sequence !== null) { - emitAsyncSequence(request, task, sequence); + emitAsyncSequence(request, task, sequence, debugInfo); } } } @@ -4335,9 +4342,10 @@ function forwardDebugInfoFromCurrentContext( task: Task, thenable: Thenable, ): void { + let debugInfo: ?ReactDebugInfo; if (__DEV__) { // If this came from Flight, forward any debug info into this new row. - const debugInfo: ?ReactDebugInfo = thenable._debugInfo; + debugInfo = thenable._debugInfo; if (debugInfo) { forwardDebugInfo(request, task, debugInfo); } @@ -4349,7 +4357,7 @@ function forwardDebugInfoFromCurrentContext( ) { const sequence = getCurrentAsyncSequence(); if (sequence !== null) { - emitAsyncSequence(request, task, sequence); + emitAsyncSequence(request, task, sequence, debugInfo); } } } From 415e44bca94235ae2fe04e63a6cd3436bf0192d1 Mon Sep 17 00:00:00 2001 From: Sebastian Markbage Date: Mon, 9 Jun 2025 22:06:24 -0400 Subject: [PATCH 07/11] Unnecessary cache --- .../ReactFlightAsyncDebugInfo-test.js | 125 +++++++++--------- 1 file changed, 60 insertions(+), 65 deletions(-) diff --git a/packages/react-server/src/__tests__/ReactFlightAsyncDebugInfo-test.js b/packages/react-server/src/__tests__/ReactFlightAsyncDebugInfo-test.js index 05f31ff7def9d..5ed6648abe58f 100644 --- a/packages/react-server/src/__tests__/ReactFlightAsyncDebugInfo-test.js +++ b/packages/react-server/src/__tests__/ReactFlightAsyncDebugInfo-test.js @@ -1047,12 +1047,7 @@ describe('ReactFlightAsyncDebugInfo', () => { }); it('can track cached entries awaited in later components', async () => { - let cacheKey; - let cacheValue; const getData = cache(async function getData(text) { - if (cacheKey === text) { - return cacheValue; - } await delay(1); return text.toUpperCase(); }); @@ -1105,7 +1100,7 @@ describe('ReactFlightAsyncDebugInfo', () => { [ "Object.", "/packages/react-server/src/__tests__/ReactFlightAsyncDebugInfo-test.js", - 1071, + 1066, 40, 1049, 62, @@ -1129,7 +1124,7 @@ describe('ReactFlightAsyncDebugInfo', () => { [ "Object.", "/packages/react-server/src/__tests__/ReactFlightAsyncDebugInfo-test.js", - 1071, + 1066, 40, 1049, 62, @@ -1148,17 +1143,17 @@ describe('ReactFlightAsyncDebugInfo', () => { [ "getData", "/packages/react-server/src/__tests__/ReactFlightAsyncDebugInfo-test.js", - 1056, + 1051, 13, - 1052, + 1050, 25, ], [ "Component", "/packages/react-server/src/__tests__/ReactFlightAsyncDebugInfo-test.js", - 1066, + 1061, 13, - 1065, + 1060, 5, ], ], @@ -1174,7 +1169,7 @@ describe('ReactFlightAsyncDebugInfo', () => { [ "Object.", "/packages/react-server/src/__tests__/ReactFlightAsyncDebugInfo-test.js", - 1071, + 1066, 40, 1049, 62, @@ -1185,17 +1180,17 @@ describe('ReactFlightAsyncDebugInfo', () => { [ "getData", "/packages/react-server/src/__tests__/ReactFlightAsyncDebugInfo-test.js", - 1056, + 1051, 13, - 1052, + 1050, 25, ], [ "Component", "/packages/react-server/src/__tests__/ReactFlightAsyncDebugInfo-test.js", - 1066, + 1061, 13, - 1065, + 1060, 5, ], ], @@ -1215,9 +1210,9 @@ describe('ReactFlightAsyncDebugInfo', () => { [ "Component", "/packages/react-server/src/__tests__/ReactFlightAsyncDebugInfo-test.js", - 1067, + 1062, 60, - 1065, + 1060, 5, ], ], @@ -1239,7 +1234,7 @@ describe('ReactFlightAsyncDebugInfo', () => { [ "Object.", "/packages/react-server/src/__tests__/ReactFlightAsyncDebugInfo-test.js", - 1071, + 1066, 40, 1049, 62, @@ -1258,17 +1253,17 @@ describe('ReactFlightAsyncDebugInfo', () => { [ "getData", "/packages/react-server/src/__tests__/ReactFlightAsyncDebugInfo-test.js", - 1056, + 1051, 13, - 1052, + 1050, 25, ], [ "Component", "/packages/react-server/src/__tests__/ReactFlightAsyncDebugInfo-test.js", - 1066, + 1061, 13, - 1065, + 1060, 5, ], ], @@ -1284,9 +1279,9 @@ describe('ReactFlightAsyncDebugInfo', () => { [ "Component", "/packages/react-server/src/__tests__/ReactFlightAsyncDebugInfo-test.js", - 1067, + 1062, 60, - 1065, + 1060, 5, ], ], @@ -1295,9 +1290,9 @@ describe('ReactFlightAsyncDebugInfo', () => { [ "Child", "/packages/react-server/src/__tests__/ReactFlightAsyncDebugInfo-test.js", - 1061, + 1056, 28, - 1060, + 1055, 5, ], ], @@ -1368,9 +1363,9 @@ describe('ReactFlightAsyncDebugInfo', () => { [ "Object.", "/packages/react-server/src/__tests__/ReactFlightAsyncDebugInfo-test.js", - 1334, + 1329, 40, - 1316, + 1311, 80, ], ], @@ -1392,9 +1387,9 @@ describe('ReactFlightAsyncDebugInfo', () => { [ "Object.", "/packages/react-server/src/__tests__/ReactFlightAsyncDebugInfo-test.js", - 1334, + 1329, 40, - 1316, + 1311, 80, ], ], @@ -1411,17 +1406,17 @@ describe('ReactFlightAsyncDebugInfo', () => { [ "delayTrice", "/packages/react-server/src/__tests__/ReactFlightAsyncDebugInfo-test.js", - 1324, + 1319, 13, - 1322, + 1317, 5, ], [ "Bar", "/packages/react-server/src/__tests__/ReactFlightAsyncDebugInfo-test.js", - 1329, + 1324, 13, - 1328, + 1323, 5, ], ], @@ -1437,9 +1432,9 @@ describe('ReactFlightAsyncDebugInfo', () => { [ "Object.", "/packages/react-server/src/__tests__/ReactFlightAsyncDebugInfo-test.js", - 1334, + 1329, 40, - 1316, + 1311, 80, ], ], @@ -1448,17 +1443,17 @@ describe('ReactFlightAsyncDebugInfo', () => { [ "delayTrice", "/packages/react-server/src/__tests__/ReactFlightAsyncDebugInfo-test.js", - 1324, + 1319, 13, - 1322, + 1317, 5, ], [ "Bar", "/packages/react-server/src/__tests__/ReactFlightAsyncDebugInfo-test.js", - 1329, + 1324, 13, - 1328, + 1323, 5, ], ], @@ -1480,9 +1475,9 @@ describe('ReactFlightAsyncDebugInfo', () => { [ "Object.", "/packages/react-server/src/__tests__/ReactFlightAsyncDebugInfo-test.js", - 1334, + 1329, 40, - 1316, + 1311, 80, ], ], @@ -1499,25 +1494,25 @@ describe('ReactFlightAsyncDebugInfo', () => { [ "delayTwice", "/packages/react-server/src/__tests__/ReactFlightAsyncDebugInfo-test.js", - 1318, + 1313, 13, - 1317, + 1312, 5, ], [ "delayTrice", "/packages/react-server/src/__tests__/ReactFlightAsyncDebugInfo-test.js", - 1323, + 1318, 15, - 1322, + 1317, 5, ], [ "Bar", "/packages/react-server/src/__tests__/ReactFlightAsyncDebugInfo-test.js", - 1329, + 1324, 13, - 1328, + 1323, 5, ], ], @@ -1533,9 +1528,9 @@ describe('ReactFlightAsyncDebugInfo', () => { [ "Object.", "/packages/react-server/src/__tests__/ReactFlightAsyncDebugInfo-test.js", - 1334, + 1329, 40, - 1316, + 1311, 80, ], ], @@ -1544,25 +1539,25 @@ describe('ReactFlightAsyncDebugInfo', () => { [ "delayTwice", "/packages/react-server/src/__tests__/ReactFlightAsyncDebugInfo-test.js", - 1318, + 1313, 13, - 1317, + 1312, 5, ], [ "delayTrice", "/packages/react-server/src/__tests__/ReactFlightAsyncDebugInfo-test.js", - 1323, + 1318, 15, - 1322, + 1317, 5, ], [ "Bar", "/packages/react-server/src/__tests__/ReactFlightAsyncDebugInfo-test.js", - 1329, + 1324, 13, - 1328, + 1323, 5, ], ], @@ -1584,9 +1579,9 @@ describe('ReactFlightAsyncDebugInfo', () => { [ "Object.", "/packages/react-server/src/__tests__/ReactFlightAsyncDebugInfo-test.js", - 1334, + 1329, 40, - 1316, + 1311, 80, ], ], @@ -1603,9 +1598,9 @@ describe('ReactFlightAsyncDebugInfo', () => { [ "delayTwice", "/packages/react-server/src/__tests__/ReactFlightAsyncDebugInfo-test.js", - 1319, + 1314, 13, - 1317, + 1312, 5, ], ], @@ -1621,9 +1616,9 @@ describe('ReactFlightAsyncDebugInfo', () => { [ "Object.", "/packages/react-server/src/__tests__/ReactFlightAsyncDebugInfo-test.js", - 1334, + 1329, 40, - 1316, + 1311, 80, ], ], @@ -1632,9 +1627,9 @@ describe('ReactFlightAsyncDebugInfo', () => { [ "delayTwice", "/packages/react-server/src/__tests__/ReactFlightAsyncDebugInfo-test.js", - 1319, + 1314, 13, - 1317, + 1312, 5, ], ], From 8df796716662a0d355cf837c60e1e5fb01c89617 Mon Sep 17 00:00:00 2001 From: Sebastian Markbage Date: Mon, 9 Jun 2025 22:05:19 -0400 Subject: [PATCH 08/11] Add a copy of the test This will be used to look at the diff to the next commit --- .../ReactFlightAsyncDebugInfo-test.js | 468 ++++++++++++++---- 1 file changed, 366 insertions(+), 102 deletions(-) diff --git a/packages/react-server/src/__tests__/ReactFlightAsyncDebugInfo-test.js b/packages/react-server/src/__tests__/ReactFlightAsyncDebugInfo-test.js index 5ed6648abe58f..30959bb90e3cb 100644 --- a/packages/react-server/src/__tests__/ReactFlightAsyncDebugInfo-test.js +++ b/packages/react-server/src/__tests__/ReactFlightAsyncDebugInfo-test.js @@ -495,6 +495,270 @@ describe('ReactFlightAsyncDebugInfo', () => { } }); + it('can track async information when use()d', async () => { + async function getData(text) { + await delay(1); + return text.toUpperCase(); + } + + async function Component() { + const result = await getData('hi'); + const moreData = getData('seb'); + return ; + } + + async function InnerComponent({text, promise}) { + // This async function depends on the I/O in parent components but it should not + // include that I/O as part of its own meta data. + return text + ', ' + (await promise); + } + + const stream = ReactServerDOMServer.renderToPipeableStream( + , + {}, + { + filterStackFrame, + }, + ); + + const readable = new Stream.PassThrough(streamOptions); + + const result = ReactServerDOMClient.createFromNodeStream(readable, { + moduleMap: {}, + moduleLoading: {}, + }); + stream.pipe(readable); + + expect(await result).toBe('HI, SEB'); + if ( + __DEV__ && + gate( + flags => + flags.enableComponentPerformanceTrack && flags.enableAsyncDebugInfo, + ) + ) { + expect(getDebugInfo(result)).toMatchInlineSnapshot(` + [ + { + "time": 0, + }, + { + "env": "Server", + "key": null, + "name": "Component", + "props": {}, + "stack": [ + [ + "Object.", + "/packages/react-server/src/__tests__/ReactFlightAsyncDebugInfo-test.js", + 517, + 40, + 498, + 49, + ], + ], + }, + { + "time": 0, + }, + { + "awaited": { + "end": 0, + "env": "Server", + "name": "delay", + "owner": { + "env": "Server", + "key": null, + "name": "Component", + "props": {}, + "stack": [ + [ + "Object.", + "/packages/react-server/src/__tests__/ReactFlightAsyncDebugInfo-test.js", + 517, + 40, + 498, + 49, + ], + ], + }, + "stack": [ + [ + "delay", + "/packages/react-server/src/__tests__/ReactFlightAsyncDebugInfo-test.js", + 133, + 12, + 132, + 3, + ], + [ + "getData", + "/packages/react-server/src/__tests__/ReactFlightAsyncDebugInfo-test.js", + 500, + 13, + 499, + 5, + ], + [ + "Component", + "/packages/react-server/src/__tests__/ReactFlightAsyncDebugInfo-test.js", + 505, + 26, + 504, + 5, + ], + ], + "start": 0, + }, + "env": "Server", + "owner": { + "env": "Server", + "key": null, + "name": "Component", + "props": {}, + "stack": [ + [ + "Object.", + "/packages/react-server/src/__tests__/ReactFlightAsyncDebugInfo-test.js", + 517, + 40, + 498, + 49, + ], + ], + }, + "stack": [ + [ + "getData", + "/packages/react-server/src/__tests__/ReactFlightAsyncDebugInfo-test.js", + 500, + 13, + 499, + 5, + ], + [ + "Component", + "/packages/react-server/src/__tests__/ReactFlightAsyncDebugInfo-test.js", + 505, + 26, + 504, + 5, + ], + ], + }, + { + "time": 0, + }, + { + "time": 0, + }, + { + "env": "Server", + "key": null, + "name": "InnerComponent", + "props": {}, + "stack": [ + [ + "Component", + "/packages/react-server/src/__tests__/ReactFlightAsyncDebugInfo-test.js", + 507, + 60, + 504, + 5, + ], + ], + }, + { + "time": 0, + }, + { + "awaited": { + "end": 0, + "env": "Server", + "name": "delay", + "owner": { + "env": "Server", + "key": null, + "name": "Component", + "props": {}, + "stack": [ + [ + "Object.", + "/packages/react-server/src/__tests__/ReactFlightAsyncDebugInfo-test.js", + 517, + 40, + 498, + 49, + ], + ], + }, + "stack": [ + [ + "delay", + "/packages/react-server/src/__tests__/ReactFlightAsyncDebugInfo-test.js", + 133, + 12, + 132, + 3, + ], + [ + "getData", + "/packages/react-server/src/__tests__/ReactFlightAsyncDebugInfo-test.js", + 500, + 13, + 499, + 5, + ], + [ + "Component", + "/packages/react-server/src/__tests__/ReactFlightAsyncDebugInfo-test.js", + 506, + 22, + 504, + 5, + ], + ], + "start": 0, + }, + "env": "Server", + "owner": { + "env": "Server", + "key": null, + "name": "InnerComponent", + "props": {}, + "stack": [ + [ + "Component", + "/packages/react-server/src/__tests__/ReactFlightAsyncDebugInfo-test.js", + 507, + 60, + 504, + 5, + ], + ], + }, + "stack": [ + [ + "InnerComponent", + "/packages/react-server/src/__tests__/ReactFlightAsyncDebugInfo-test.js", + 513, + 35, + 510, + 5, + ], + ], + }, + { + "time": 0, + }, + { + "time": 0, + }, + ] + `); + } + }); + it('can track the start of I/O when no native promise is used', async () => { function Component() { const callbacks = []; @@ -540,9 +804,9 @@ describe('ReactFlightAsyncDebugInfo', () => { [ "Object.", "/packages/react-server/src/__tests__/ReactFlightAsyncDebugInfo-test.js", - 511, + 775, 109, - 498, + 762, 67, ], ], @@ -561,9 +825,9 @@ describe('ReactFlightAsyncDebugInfo', () => { [ "Object.", "/packages/react-server/src/__tests__/ReactFlightAsyncDebugInfo-test.js", - 511, + 775, 109, - 498, + 762, 67, ], ], @@ -572,9 +836,9 @@ describe('ReactFlightAsyncDebugInfo', () => { [ "Component", "/packages/react-server/src/__tests__/ReactFlightAsyncDebugInfo-test.js", - 501, + 765, 7, - 499, + 763, 5, ], ], @@ -634,9 +898,9 @@ describe('ReactFlightAsyncDebugInfo', () => { [ "Object.", "/packages/react-server/src/__tests__/ReactFlightAsyncDebugInfo-test.js", - 605, + 869, 109, - 596, + 860, 94, ], ], @@ -705,9 +969,9 @@ describe('ReactFlightAsyncDebugInfo', () => { [ "Object.", "/packages/react-server/src/__tests__/ReactFlightAsyncDebugInfo-test.js", - 676, + 940, 109, - 652, + 916, 50, ], ], @@ -787,9 +1051,9 @@ describe('ReactFlightAsyncDebugInfo', () => { [ "Object.", "/packages/react-server/src/__tests__/ReactFlightAsyncDebugInfo-test.js", - 758, + 1022, 109, - 741, + 1005, 63, ], ], @@ -814,9 +1078,9 @@ describe('ReactFlightAsyncDebugInfo', () => { [ "Component", "/packages/react-server/src/__tests__/ReactFlightAsyncDebugInfo-test.js", - 754, + 1018, 24, - 753, + 1017, 5, ], ], @@ -846,9 +1110,9 @@ describe('ReactFlightAsyncDebugInfo', () => { [ "Component", "/packages/react-server/src/__tests__/ReactFlightAsyncDebugInfo-test.js", - 754, + 1018, 24, - 753, + 1017, 5, ], ], @@ -865,17 +1129,17 @@ describe('ReactFlightAsyncDebugInfo', () => { [ "getData", "/packages/react-server/src/__tests__/ReactFlightAsyncDebugInfo-test.js", - 743, + 1007, 13, - 742, + 1006, 5, ], [ "ThirdPartyComponent", "/packages/react-server/src/__tests__/ReactFlightAsyncDebugInfo-test.js", - 749, + 1013, 24, - 748, + 1012, 5, ], ], @@ -899,9 +1163,9 @@ describe('ReactFlightAsyncDebugInfo', () => { [ "Component", "/packages/react-server/src/__tests__/ReactFlightAsyncDebugInfo-test.js", - 754, + 1018, 24, - 753, + 1017, 5, ], ], @@ -910,17 +1174,17 @@ describe('ReactFlightAsyncDebugInfo', () => { [ "getData", "/packages/react-server/src/__tests__/ReactFlightAsyncDebugInfo-test.js", - 743, + 1007, 13, - 742, + 1006, 5, ], [ "ThirdPartyComponent", "/packages/react-server/src/__tests__/ReactFlightAsyncDebugInfo-test.js", - 749, + 1013, 24, - 748, + 1012, 5, ], ], @@ -953,9 +1217,9 @@ describe('ReactFlightAsyncDebugInfo', () => { [ "Component", "/packages/react-server/src/__tests__/ReactFlightAsyncDebugInfo-test.js", - 754, + 1018, 24, - 753, + 1017, 5, ], ], @@ -972,17 +1236,17 @@ describe('ReactFlightAsyncDebugInfo', () => { [ "getData", "/packages/react-server/src/__tests__/ReactFlightAsyncDebugInfo-test.js", - 744, + 1008, 13, - 742, + 1006, 5, ], [ "ThirdPartyComponent", "/packages/react-server/src/__tests__/ReactFlightAsyncDebugInfo-test.js", - 749, + 1013, 18, - 748, + 1012, 5, ], ], @@ -1006,9 +1270,9 @@ describe('ReactFlightAsyncDebugInfo', () => { [ "Component", "/packages/react-server/src/__tests__/ReactFlightAsyncDebugInfo-test.js", - 754, + 1018, 24, - 753, + 1017, 5, ], ], @@ -1017,17 +1281,17 @@ describe('ReactFlightAsyncDebugInfo', () => { [ "getData", "/packages/react-server/src/__tests__/ReactFlightAsyncDebugInfo-test.js", - 744, + 1008, 13, - 742, + 1006, 5, ], [ "ThirdPartyComponent", "/packages/react-server/src/__tests__/ReactFlightAsyncDebugInfo-test.js", - 749, + 1013, 18, - 748, + 1012, 5, ], ], @@ -1100,9 +1364,9 @@ describe('ReactFlightAsyncDebugInfo', () => { [ "Object.", "/packages/react-server/src/__tests__/ReactFlightAsyncDebugInfo-test.js", - 1066, + 1330, 40, - 1049, + 1313, 62, ], ], @@ -1124,9 +1388,9 @@ describe('ReactFlightAsyncDebugInfo', () => { [ "Object.", "/packages/react-server/src/__tests__/ReactFlightAsyncDebugInfo-test.js", - 1066, + 1330, 40, - 1049, + 1313, 62, ], ], @@ -1143,17 +1407,17 @@ describe('ReactFlightAsyncDebugInfo', () => { [ "getData", "/packages/react-server/src/__tests__/ReactFlightAsyncDebugInfo-test.js", - 1051, + 1315, 13, - 1050, + 1314, 25, ], [ "Component", "/packages/react-server/src/__tests__/ReactFlightAsyncDebugInfo-test.js", - 1061, + 1325, 13, - 1060, + 1324, 5, ], ], @@ -1169,9 +1433,9 @@ describe('ReactFlightAsyncDebugInfo', () => { [ "Object.", "/packages/react-server/src/__tests__/ReactFlightAsyncDebugInfo-test.js", - 1066, + 1330, 40, - 1049, + 1313, 62, ], ], @@ -1180,17 +1444,17 @@ describe('ReactFlightAsyncDebugInfo', () => { [ "getData", "/packages/react-server/src/__tests__/ReactFlightAsyncDebugInfo-test.js", - 1051, + 1315, 13, - 1050, + 1314, 25, ], [ "Component", "/packages/react-server/src/__tests__/ReactFlightAsyncDebugInfo-test.js", - 1061, + 1325, 13, - 1060, + 1324, 5, ], ], @@ -1210,9 +1474,9 @@ describe('ReactFlightAsyncDebugInfo', () => { [ "Component", "/packages/react-server/src/__tests__/ReactFlightAsyncDebugInfo-test.js", - 1062, + 1326, 60, - 1060, + 1324, 5, ], ], @@ -1234,9 +1498,9 @@ describe('ReactFlightAsyncDebugInfo', () => { [ "Object.", "/packages/react-server/src/__tests__/ReactFlightAsyncDebugInfo-test.js", - 1066, + 1330, 40, - 1049, + 1313, 62, ], ], @@ -1253,17 +1517,17 @@ describe('ReactFlightAsyncDebugInfo', () => { [ "getData", "/packages/react-server/src/__tests__/ReactFlightAsyncDebugInfo-test.js", - 1051, + 1315, 13, - 1050, + 1314, 25, ], [ "Component", "/packages/react-server/src/__tests__/ReactFlightAsyncDebugInfo-test.js", - 1061, + 1325, 13, - 1060, + 1324, 5, ], ], @@ -1279,9 +1543,9 @@ describe('ReactFlightAsyncDebugInfo', () => { [ "Component", "/packages/react-server/src/__tests__/ReactFlightAsyncDebugInfo-test.js", - 1062, + 1326, 60, - 1060, + 1324, 5, ], ], @@ -1290,9 +1554,9 @@ describe('ReactFlightAsyncDebugInfo', () => { [ "Child", "/packages/react-server/src/__tests__/ReactFlightAsyncDebugInfo-test.js", - 1056, + 1320, 28, - 1055, + 1319, 5, ], ], @@ -1363,9 +1627,9 @@ describe('ReactFlightAsyncDebugInfo', () => { [ "Object.", "/packages/react-server/src/__tests__/ReactFlightAsyncDebugInfo-test.js", - 1329, + 1593, 40, - 1311, + 1575, 80, ], ], @@ -1387,9 +1651,9 @@ describe('ReactFlightAsyncDebugInfo', () => { [ "Object.", "/packages/react-server/src/__tests__/ReactFlightAsyncDebugInfo-test.js", - 1329, + 1593, 40, - 1311, + 1575, 80, ], ], @@ -1406,17 +1670,17 @@ describe('ReactFlightAsyncDebugInfo', () => { [ "delayTrice", "/packages/react-server/src/__tests__/ReactFlightAsyncDebugInfo-test.js", - 1319, + 1583, 13, - 1317, + 1581, 5, ], [ "Bar", "/packages/react-server/src/__tests__/ReactFlightAsyncDebugInfo-test.js", - 1324, + 1588, 13, - 1323, + 1587, 5, ], ], @@ -1432,9 +1696,9 @@ describe('ReactFlightAsyncDebugInfo', () => { [ "Object.", "/packages/react-server/src/__tests__/ReactFlightAsyncDebugInfo-test.js", - 1329, + 1593, 40, - 1311, + 1575, 80, ], ], @@ -1443,17 +1707,17 @@ describe('ReactFlightAsyncDebugInfo', () => { [ "delayTrice", "/packages/react-server/src/__tests__/ReactFlightAsyncDebugInfo-test.js", - 1319, + 1583, 13, - 1317, + 1581, 5, ], [ "Bar", "/packages/react-server/src/__tests__/ReactFlightAsyncDebugInfo-test.js", - 1324, + 1588, 13, - 1323, + 1587, 5, ], ], @@ -1475,9 +1739,9 @@ describe('ReactFlightAsyncDebugInfo', () => { [ "Object.", "/packages/react-server/src/__tests__/ReactFlightAsyncDebugInfo-test.js", - 1329, + 1593, 40, - 1311, + 1575, 80, ], ], @@ -1494,25 +1758,25 @@ describe('ReactFlightAsyncDebugInfo', () => { [ "delayTwice", "/packages/react-server/src/__tests__/ReactFlightAsyncDebugInfo-test.js", - 1313, + 1577, 13, - 1312, + 1576, 5, ], [ "delayTrice", "/packages/react-server/src/__tests__/ReactFlightAsyncDebugInfo-test.js", - 1318, + 1582, 15, - 1317, + 1581, 5, ], [ "Bar", "/packages/react-server/src/__tests__/ReactFlightAsyncDebugInfo-test.js", - 1324, + 1588, 13, - 1323, + 1587, 5, ], ], @@ -1528,9 +1792,9 @@ describe('ReactFlightAsyncDebugInfo', () => { [ "Object.", "/packages/react-server/src/__tests__/ReactFlightAsyncDebugInfo-test.js", - 1329, + 1593, 40, - 1311, + 1575, 80, ], ], @@ -1539,25 +1803,25 @@ describe('ReactFlightAsyncDebugInfo', () => { [ "delayTwice", "/packages/react-server/src/__tests__/ReactFlightAsyncDebugInfo-test.js", - 1313, + 1577, 13, - 1312, + 1576, 5, ], [ "delayTrice", "/packages/react-server/src/__tests__/ReactFlightAsyncDebugInfo-test.js", - 1318, + 1582, 15, - 1317, + 1581, 5, ], [ "Bar", "/packages/react-server/src/__tests__/ReactFlightAsyncDebugInfo-test.js", - 1324, + 1588, 13, - 1323, + 1587, 5, ], ], @@ -1579,9 +1843,9 @@ describe('ReactFlightAsyncDebugInfo', () => { [ "Object.", "/packages/react-server/src/__tests__/ReactFlightAsyncDebugInfo-test.js", - 1329, + 1593, 40, - 1311, + 1575, 80, ], ], @@ -1598,9 +1862,9 @@ describe('ReactFlightAsyncDebugInfo', () => { [ "delayTwice", "/packages/react-server/src/__tests__/ReactFlightAsyncDebugInfo-test.js", - 1314, + 1578, 13, - 1312, + 1576, 5, ], ], @@ -1616,9 +1880,9 @@ describe('ReactFlightAsyncDebugInfo', () => { [ "Object.", "/packages/react-server/src/__tests__/ReactFlightAsyncDebugInfo-test.js", - 1329, + 1593, 40, - 1311, + 1575, 80, ], ], @@ -1627,9 +1891,9 @@ describe('ReactFlightAsyncDebugInfo', () => { [ "delayTwice", "/packages/react-server/src/__tests__/ReactFlightAsyncDebugInfo-test.js", - 1314, + 1578, 13, - 1312, + 1576, 5, ], ], From 1d6e4b1d02b282d617c9500571505209beaedf7b Mon Sep 17 00:00:00 2001 From: Sebastian Markbage Date: Tue, 10 Jun 2025 00:23:13 -0400 Subject: [PATCH 09/11] Switch to use() The owner/stack disappears in this case because the use() is not a real await. --- .../ReactFlightAsyncDebugInfo-test.js | 41 +++---------------- 1 file changed, 6 insertions(+), 35 deletions(-) diff --git a/packages/react-server/src/__tests__/ReactFlightAsyncDebugInfo-test.js b/packages/react-server/src/__tests__/ReactFlightAsyncDebugInfo-test.js index 30959bb90e3cb..a56d06c9a2f37 100644 --- a/packages/react-server/src/__tests__/ReactFlightAsyncDebugInfo-test.js +++ b/packages/react-server/src/__tests__/ReactFlightAsyncDebugInfo-test.js @@ -501,16 +501,16 @@ describe('ReactFlightAsyncDebugInfo', () => { return text.toUpperCase(); } - async function Component() { - const result = await getData('hi'); + function Component() { + const result = ReactServer.use(getData('hi')); const moreData = getData('seb'); return ; } - async function InnerComponent({text, promise}) { + function InnerComponent({text, promise}) { // This async function depends on the I/O in parent components but it should not // include that I/O as part of its own meta data. - return text + ', ' + (await promise); + return text + ', ' + ReactServer.use(promise); } const stream = ReactServerDOMServer.renderToPipeableStream( @@ -603,7 +603,7 @@ describe('ReactFlightAsyncDebugInfo', () => { "Component", "/packages/react-server/src/__tests__/ReactFlightAsyncDebugInfo-test.js", 505, - 26, + 36, 504, 5, ], @@ -640,7 +640,7 @@ describe('ReactFlightAsyncDebugInfo', () => { "Component", "/packages/react-server/src/__tests__/ReactFlightAsyncDebugInfo-test.js", 505, - 26, + 36, 504, 5, ], @@ -668,9 +668,6 @@ describe('ReactFlightAsyncDebugInfo', () => { ], ], }, - { - "time": 0, - }, { "awaited": { "end": 0, @@ -721,32 +718,6 @@ describe('ReactFlightAsyncDebugInfo', () => { "start": 0, }, "env": "Server", - "owner": { - "env": "Server", - "key": null, - "name": "InnerComponent", - "props": {}, - "stack": [ - [ - "Component", - "/packages/react-server/src/__tests__/ReactFlightAsyncDebugInfo-test.js", - 507, - 60, - 504, - 5, - ], - ], - }, - "stack": [ - [ - "InnerComponent", - "/packages/react-server/src/__tests__/ReactFlightAsyncDebugInfo-test.js", - 513, - 35, - 510, - 5, - ], - ], }, { "time": 0, From a893c631f09b2b0c07ddc7908c1652a876fa91b8 Mon Sep 17 00:00:00 2001 From: Sebastian Markbage Date: Tue, 10 Jun 2025 00:25:52 -0400 Subject: [PATCH 10/11] Test extracting data from an already instrumented Promise --- .../ReactFlightAsyncDebugInfo-test.js | 436 ++++++++++++++---- 1 file changed, 334 insertions(+), 102 deletions(-) diff --git a/packages/react-server/src/__tests__/ReactFlightAsyncDebugInfo-test.js b/packages/react-server/src/__tests__/ReactFlightAsyncDebugInfo-test.js index a56d06c9a2f37..21f64ae3100b2 100644 --- a/packages/react-server/src/__tests__/ReactFlightAsyncDebugInfo-test.js +++ b/packages/react-server/src/__tests__/ReactFlightAsyncDebugInfo-test.js @@ -775,9 +775,9 @@ describe('ReactFlightAsyncDebugInfo', () => { [ "Object.", "/packages/react-server/src/__tests__/ReactFlightAsyncDebugInfo-test.js", - 775, + 746, 109, - 762, + 733, 67, ], ], @@ -796,9 +796,9 @@ describe('ReactFlightAsyncDebugInfo', () => { [ "Object.", "/packages/react-server/src/__tests__/ReactFlightAsyncDebugInfo-test.js", - 775, + 746, 109, - 762, + 733, 67, ], ], @@ -807,9 +807,9 @@ describe('ReactFlightAsyncDebugInfo', () => { [ "Component", "/packages/react-server/src/__tests__/ReactFlightAsyncDebugInfo-test.js", - 765, + 736, 7, - 763, + 734, 5, ], ], @@ -869,9 +869,9 @@ describe('ReactFlightAsyncDebugInfo', () => { [ "Object.", "/packages/react-server/src/__tests__/ReactFlightAsyncDebugInfo-test.js", - 869, + 840, 109, - 860, + 831, 94, ], ], @@ -940,9 +940,9 @@ describe('ReactFlightAsyncDebugInfo', () => { [ "Object.", "/packages/react-server/src/__tests__/ReactFlightAsyncDebugInfo-test.js", - 940, + 911, 109, - 916, + 887, 50, ], ], @@ -1022,9 +1022,9 @@ describe('ReactFlightAsyncDebugInfo', () => { [ "Object.", "/packages/react-server/src/__tests__/ReactFlightAsyncDebugInfo-test.js", - 1022, + 993, 109, - 1005, + 976, 63, ], ], @@ -1049,9 +1049,9 @@ describe('ReactFlightAsyncDebugInfo', () => { [ "Component", "/packages/react-server/src/__tests__/ReactFlightAsyncDebugInfo-test.js", - 1018, + 989, 24, - 1017, + 988, 5, ], ], @@ -1081,9 +1081,9 @@ describe('ReactFlightAsyncDebugInfo', () => { [ "Component", "/packages/react-server/src/__tests__/ReactFlightAsyncDebugInfo-test.js", - 1018, + 989, 24, - 1017, + 988, 5, ], ], @@ -1100,17 +1100,17 @@ describe('ReactFlightAsyncDebugInfo', () => { [ "getData", "/packages/react-server/src/__tests__/ReactFlightAsyncDebugInfo-test.js", - 1007, + 978, 13, - 1006, + 977, 5, ], [ "ThirdPartyComponent", "/packages/react-server/src/__tests__/ReactFlightAsyncDebugInfo-test.js", - 1013, + 984, 24, - 1012, + 983, 5, ], ], @@ -1134,9 +1134,9 @@ describe('ReactFlightAsyncDebugInfo', () => { [ "Component", "/packages/react-server/src/__tests__/ReactFlightAsyncDebugInfo-test.js", - 1018, + 989, 24, - 1017, + 988, 5, ], ], @@ -1145,17 +1145,17 @@ describe('ReactFlightAsyncDebugInfo', () => { [ "getData", "/packages/react-server/src/__tests__/ReactFlightAsyncDebugInfo-test.js", - 1007, + 978, 13, - 1006, + 977, 5, ], [ "ThirdPartyComponent", "/packages/react-server/src/__tests__/ReactFlightAsyncDebugInfo-test.js", - 1013, + 984, 24, - 1012, + 983, 5, ], ], @@ -1188,9 +1188,9 @@ describe('ReactFlightAsyncDebugInfo', () => { [ "Component", "/packages/react-server/src/__tests__/ReactFlightAsyncDebugInfo-test.js", - 1018, + 989, 24, - 1017, + 988, 5, ], ], @@ -1207,17 +1207,17 @@ describe('ReactFlightAsyncDebugInfo', () => { [ "getData", "/packages/react-server/src/__tests__/ReactFlightAsyncDebugInfo-test.js", - 1008, + 979, 13, - 1006, + 977, 5, ], [ "ThirdPartyComponent", "/packages/react-server/src/__tests__/ReactFlightAsyncDebugInfo-test.js", - 1013, + 984, 18, - 1012, + 983, 5, ], ], @@ -1241,9 +1241,9 @@ describe('ReactFlightAsyncDebugInfo', () => { [ "Component", "/packages/react-server/src/__tests__/ReactFlightAsyncDebugInfo-test.js", - 1018, + 989, 24, - 1017, + 988, 5, ], ], @@ -1252,17 +1252,17 @@ describe('ReactFlightAsyncDebugInfo', () => { [ "getData", "/packages/react-server/src/__tests__/ReactFlightAsyncDebugInfo-test.js", - 1008, + 979, 13, - 1006, + 977, 5, ], [ "ThirdPartyComponent", "/packages/react-server/src/__tests__/ReactFlightAsyncDebugInfo-test.js", - 1013, + 984, 18, - 1012, + 983, 5, ], ], @@ -1335,9 +1335,9 @@ describe('ReactFlightAsyncDebugInfo', () => { [ "Object.", "/packages/react-server/src/__tests__/ReactFlightAsyncDebugInfo-test.js", - 1330, + 1301, 40, - 1313, + 1284, 62, ], ], @@ -1359,9 +1359,9 @@ describe('ReactFlightAsyncDebugInfo', () => { [ "Object.", "/packages/react-server/src/__tests__/ReactFlightAsyncDebugInfo-test.js", - 1330, + 1301, 40, - 1313, + 1284, 62, ], ], @@ -1378,17 +1378,17 @@ describe('ReactFlightAsyncDebugInfo', () => { [ "getData", "/packages/react-server/src/__tests__/ReactFlightAsyncDebugInfo-test.js", - 1315, + 1286, 13, - 1314, + 1285, 25, ], [ "Component", "/packages/react-server/src/__tests__/ReactFlightAsyncDebugInfo-test.js", - 1325, + 1296, 13, - 1324, + 1295, 5, ], ], @@ -1404,9 +1404,9 @@ describe('ReactFlightAsyncDebugInfo', () => { [ "Object.", "/packages/react-server/src/__tests__/ReactFlightAsyncDebugInfo-test.js", - 1330, + 1301, 40, - 1313, + 1284, 62, ], ], @@ -1415,17 +1415,17 @@ describe('ReactFlightAsyncDebugInfo', () => { [ "getData", "/packages/react-server/src/__tests__/ReactFlightAsyncDebugInfo-test.js", - 1315, + 1286, 13, - 1314, + 1285, 25, ], [ "Component", "/packages/react-server/src/__tests__/ReactFlightAsyncDebugInfo-test.js", - 1325, + 1296, 13, - 1324, + 1295, 5, ], ], @@ -1445,9 +1445,9 @@ describe('ReactFlightAsyncDebugInfo', () => { [ "Component", "/packages/react-server/src/__tests__/ReactFlightAsyncDebugInfo-test.js", - 1326, + 1297, 60, - 1324, + 1295, 5, ], ], @@ -1469,9 +1469,9 @@ describe('ReactFlightAsyncDebugInfo', () => { [ "Object.", "/packages/react-server/src/__tests__/ReactFlightAsyncDebugInfo-test.js", - 1330, + 1301, 40, - 1313, + 1284, 62, ], ], @@ -1488,17 +1488,17 @@ describe('ReactFlightAsyncDebugInfo', () => { [ "getData", "/packages/react-server/src/__tests__/ReactFlightAsyncDebugInfo-test.js", - 1315, + 1286, 13, - 1314, + 1285, 25, ], [ "Component", "/packages/react-server/src/__tests__/ReactFlightAsyncDebugInfo-test.js", - 1325, + 1296, 13, - 1324, + 1295, 5, ], ], @@ -1514,9 +1514,9 @@ describe('ReactFlightAsyncDebugInfo', () => { [ "Component", "/packages/react-server/src/__tests__/ReactFlightAsyncDebugInfo-test.js", - 1326, + 1297, 60, - 1324, + 1295, 5, ], ], @@ -1525,9 +1525,9 @@ describe('ReactFlightAsyncDebugInfo', () => { [ "Child", "/packages/react-server/src/__tests__/ReactFlightAsyncDebugInfo-test.js", - 1320, + 1291, 28, - 1319, + 1290, 5, ], ], @@ -1543,6 +1543,238 @@ describe('ReactFlightAsyncDebugInfo', () => { } }); + it('can track cached entries used in child position', async () => { + const getData = cache(async function getData(text) { + await delay(1); + return text.toUpperCase(); + }); + + function Child() { + return getData('hi'); + } + + function Component() { + ReactServer.use(getData('hi')); + return ; + } + + const stream = ReactServerDOMServer.renderToPipeableStream( + , + {}, + { + filterStackFrame, + }, + ); + + const readable = new Stream.PassThrough(streamOptions); + + const result = ReactServerDOMClient.createFromNodeStream(readable, { + moduleMap: {}, + moduleLoading: {}, + }); + stream.pipe(readable); + + expect(await result).toBe('HI'); + if ( + __DEV__ && + gate( + flags => + flags.enableComponentPerformanceTrack && flags.enableAsyncDebugInfo, + ) + ) { + expect(getDebugInfo(result)).toMatchInlineSnapshot(` + [ + { + "time": 0, + }, + { + "env": "Server", + "key": null, + "name": "Component", + "props": {}, + "stack": [ + [ + "Object.", + "/packages/react-server/src/__tests__/ReactFlightAsyncDebugInfo-test.js", + 1562, + 40, + 1546, + 57, + ], + ], + }, + { + "time": 0, + }, + { + "awaited": { + "end": 0, + "env": "Server", + "name": "delay", + "owner": { + "env": "Server", + "key": null, + "name": "Component", + "props": {}, + "stack": [ + [ + "Object.", + "/packages/react-server/src/__tests__/ReactFlightAsyncDebugInfo-test.js", + 1562, + 40, + 1546, + 57, + ], + ], + }, + "stack": [ + [ + "delay", + "/packages/react-server/src/__tests__/ReactFlightAsyncDebugInfo-test.js", + 133, + 12, + 132, + 3, + ], + [ + "getData", + "/packages/react-server/src/__tests__/ReactFlightAsyncDebugInfo-test.js", + 1548, + 13, + 1547, + 25, + ], + [ + "Component", + "/packages/react-server/src/__tests__/ReactFlightAsyncDebugInfo-test.js", + 1557, + 23, + 1556, + 5, + ], + ], + "start": 0, + }, + "env": "Server", + "owner": { + "env": "Server", + "key": null, + "name": "Component", + "props": {}, + "stack": [ + [ + "Object.", + "/packages/react-server/src/__tests__/ReactFlightAsyncDebugInfo-test.js", + 1562, + 40, + 1546, + 57, + ], + ], + }, + "stack": [ + [ + "getData", + "/packages/react-server/src/__tests__/ReactFlightAsyncDebugInfo-test.js", + 1548, + 13, + 1547, + 25, + ], + [ + "Component", + "/packages/react-server/src/__tests__/ReactFlightAsyncDebugInfo-test.js", + 1557, + 23, + 1556, + 5, + ], + ], + }, + { + "time": 0, + }, + { + "time": 0, + }, + { + "env": "Server", + "key": null, + "name": "Child", + "props": {}, + "stack": [ + [ + "Component", + "/packages/react-server/src/__tests__/ReactFlightAsyncDebugInfo-test.js", + 1558, + 60, + 1556, + 5, + ], + ], + }, + { + "awaited": { + "end": 0, + "env": "Server", + "name": "delay", + "owner": { + "env": "Server", + "key": null, + "name": "Component", + "props": {}, + "stack": [ + [ + "Object.", + "/packages/react-server/src/__tests__/ReactFlightAsyncDebugInfo-test.js", + 1562, + 40, + 1546, + 57, + ], + ], + }, + "stack": [ + [ + "delay", + "/packages/react-server/src/__tests__/ReactFlightAsyncDebugInfo-test.js", + 133, + 12, + 132, + 3, + ], + [ + "getData", + "/packages/react-server/src/__tests__/ReactFlightAsyncDebugInfo-test.js", + 1548, + 13, + 1547, + 25, + ], + [ + "Component", + "/packages/react-server/src/__tests__/ReactFlightAsyncDebugInfo-test.js", + 1557, + 23, + 1556, + 5, + ], + ], + "start": 0, + }, + "env": "Server", + }, + { + "time": 0, + }, + { + "time": 0, + }, + ] + `); + } + }); + it('can track implicit returned promises that are blocked by previous data', async () => { async function delayTwice() { await delay('', 20); @@ -1598,9 +1830,9 @@ describe('ReactFlightAsyncDebugInfo', () => { [ "Object.", "/packages/react-server/src/__tests__/ReactFlightAsyncDebugInfo-test.js", - 1593, + 1796, 40, - 1575, + 1778, 80, ], ], @@ -1622,9 +1854,9 @@ describe('ReactFlightAsyncDebugInfo', () => { [ "Object.", "/packages/react-server/src/__tests__/ReactFlightAsyncDebugInfo-test.js", - 1593, + 1796, 40, - 1575, + 1778, 80, ], ], @@ -1641,17 +1873,17 @@ describe('ReactFlightAsyncDebugInfo', () => { [ "delayTrice", "/packages/react-server/src/__tests__/ReactFlightAsyncDebugInfo-test.js", - 1583, + 1786, 13, - 1581, + 1784, 5, ], [ "Bar", "/packages/react-server/src/__tests__/ReactFlightAsyncDebugInfo-test.js", - 1588, + 1791, 13, - 1587, + 1790, 5, ], ], @@ -1667,9 +1899,9 @@ describe('ReactFlightAsyncDebugInfo', () => { [ "Object.", "/packages/react-server/src/__tests__/ReactFlightAsyncDebugInfo-test.js", - 1593, + 1796, 40, - 1575, + 1778, 80, ], ], @@ -1678,17 +1910,17 @@ describe('ReactFlightAsyncDebugInfo', () => { [ "delayTrice", "/packages/react-server/src/__tests__/ReactFlightAsyncDebugInfo-test.js", - 1583, + 1786, 13, - 1581, + 1784, 5, ], [ "Bar", "/packages/react-server/src/__tests__/ReactFlightAsyncDebugInfo-test.js", - 1588, + 1791, 13, - 1587, + 1790, 5, ], ], @@ -1710,9 +1942,9 @@ describe('ReactFlightAsyncDebugInfo', () => { [ "Object.", "/packages/react-server/src/__tests__/ReactFlightAsyncDebugInfo-test.js", - 1593, + 1796, 40, - 1575, + 1778, 80, ], ], @@ -1729,25 +1961,25 @@ describe('ReactFlightAsyncDebugInfo', () => { [ "delayTwice", "/packages/react-server/src/__tests__/ReactFlightAsyncDebugInfo-test.js", - 1577, + 1780, 13, - 1576, + 1779, 5, ], [ "delayTrice", "/packages/react-server/src/__tests__/ReactFlightAsyncDebugInfo-test.js", - 1582, + 1785, 15, - 1581, + 1784, 5, ], [ "Bar", "/packages/react-server/src/__tests__/ReactFlightAsyncDebugInfo-test.js", - 1588, + 1791, 13, - 1587, + 1790, 5, ], ], @@ -1763,9 +1995,9 @@ describe('ReactFlightAsyncDebugInfo', () => { [ "Object.", "/packages/react-server/src/__tests__/ReactFlightAsyncDebugInfo-test.js", - 1593, + 1796, 40, - 1575, + 1778, 80, ], ], @@ -1774,25 +2006,25 @@ describe('ReactFlightAsyncDebugInfo', () => { [ "delayTwice", "/packages/react-server/src/__tests__/ReactFlightAsyncDebugInfo-test.js", - 1577, + 1780, 13, - 1576, + 1779, 5, ], [ "delayTrice", "/packages/react-server/src/__tests__/ReactFlightAsyncDebugInfo-test.js", - 1582, + 1785, 15, - 1581, + 1784, 5, ], [ "Bar", "/packages/react-server/src/__tests__/ReactFlightAsyncDebugInfo-test.js", - 1588, + 1791, 13, - 1587, + 1790, 5, ], ], @@ -1814,9 +2046,9 @@ describe('ReactFlightAsyncDebugInfo', () => { [ "Object.", "/packages/react-server/src/__tests__/ReactFlightAsyncDebugInfo-test.js", - 1593, + 1796, 40, - 1575, + 1778, 80, ], ], @@ -1833,9 +2065,9 @@ describe('ReactFlightAsyncDebugInfo', () => { [ "delayTwice", "/packages/react-server/src/__tests__/ReactFlightAsyncDebugInfo-test.js", - 1578, + 1781, 13, - 1576, + 1779, 5, ], ], @@ -1851,9 +2083,9 @@ describe('ReactFlightAsyncDebugInfo', () => { [ "Object.", "/packages/react-server/src/__tests__/ReactFlightAsyncDebugInfo-test.js", - 1593, + 1796, 40, - 1575, + 1778, 80, ], ], @@ -1862,9 +2094,9 @@ describe('ReactFlightAsyncDebugInfo', () => { [ "delayTwice", "/packages/react-server/src/__tests__/ReactFlightAsyncDebugInfo-test.js", - 1578, + 1781, 13, - 1576, + 1779, 5, ], ], From 0943f8b4ce0c8baf50a9294ee7f0c075e5f26e41 Mon Sep 17 00:00:00 2001 From: Sebastian Markbage Date: Tue, 10 Jun 2025 01:00:30 -0400 Subject: [PATCH 11/11] Track stack of use() calls Then pass this as the default if there is no deeper await. This means that use() behaves similar to await. --- .../react-server/src/ReactFlightServer.js | 46 ++- .../react-server/src/ReactFlightThenable.js | 7 + .../ReactFlightAsyncDebugInfo-test.js | 274 ++++++++++-------- 3 files changed, 194 insertions(+), 133 deletions(-) diff --git a/packages/react-server/src/ReactFlightServer.js b/packages/react-server/src/ReactFlightServer.js index a385fbb05b015..e51197c5b2b19 100644 --- a/packages/react-server/src/ReactFlightServer.js +++ b/packages/react-server/src/ReactFlightServer.js @@ -692,14 +692,14 @@ function serializeThenable( switch (thenable.status) { case 'fulfilled': { - forwardDebugInfoFromThenable(request, newTask, thenable); + forwardDebugInfoFromThenable(request, newTask, thenable, null, null); // We have the resolved value, we can go ahead and schedule it for serialization. newTask.model = thenable.value; pingTask(request, newTask); return newTask.id; } case 'rejected': { - forwardDebugInfoFromThenable(request, newTask, thenable); + forwardDebugInfoFromThenable(request, newTask, thenable, null, null); const x = thenable.reason; erroredTask(request, newTask, x); return newTask.id; @@ -1042,11 +1042,11 @@ function createLazyWrapperAroundWakeable( const thenable: Thenable = (wakeable: any); switch (thenable.status) { case 'fulfilled': { - forwardDebugInfoFromThenable(request, task, thenable); + forwardDebugInfoFromThenable(request, task, thenable, null, null); return thenable.value; } case 'rejected': - forwardDebugInfoFromThenable(request, task, thenable); + forwardDebugInfoFromThenable(request, task, thenable, null, null); break; default: { if (typeof thenable.status === 'string') { @@ -1366,6 +1366,7 @@ function renderFunctionComponent( } } } else { + componentDebugInfo = (null: any); prepareToUseHooksForComponent(prevThenableState, null); // The secondArg is always undefined in Server Components since refs error early. const secondArg = undefined; @@ -1398,8 +1399,20 @@ function renderFunctionComponent( // We do this at the end so that we don't keep doing this for each retry. const trackedThenables = getTrackedThenablesAfterRendering(); if (trackedThenables !== null) { + const stacks: Array = + __DEV__ && enableAsyncDebugInfo + ? (trackedThenables: any)._stacks || + ((trackedThenables: any)._stacks = []) + : (null: any); for (let i = 0; i < trackedThenables.length; i++) { - forwardDebugInfoFromThenable(request, task, trackedThenables[i]); + const stack = __DEV__ && enableAsyncDebugInfo ? stacks[i] : null; + forwardDebugInfoFromThenable( + request, + task, + trackedThenables[i], + __DEV__ ? componentDebugInfo : null, + stack, + ); } } } @@ -2019,6 +2032,8 @@ function emitAsyncSequence( task: Task, node: AsyncSequence, alreadyForwardedDebugInfo: ?ReactDebugInfo, + owner: null | ReactComponentInfo, + stack: null | Error, ): void { const visited: Set = new Set(); if (__DEV__ && alreadyForwardedDebugInfo) { @@ -2034,10 +2049,21 @@ function emitAsyncSequence( const env = (0, request.environmentName)(); // If we don't have any thing awaited, the time we started awaiting was internal // when we yielded after rendering. The current task time is basically that. - emitDebugChunk(request, task.id, { + const debugInfo: ReactAsyncInfo = { awaited: ((awaitedNode: any): ReactIOInfo), // This is deduped by this reference. env: env, - }); + }; + if (__DEV__) { + if (owner != null) { + // $FlowFixMe[cannot-write] + debugInfo.owner = owner; + } + if (stack != null) { + // $FlowFixMe[cannot-write] + debugInfo.stack = filterStackTrace(request, parseStackTrace(stack, 1)); + } + } + emitDebugChunk(request, task.id, debugInfo); markOperationEndTime(request, task, awaitedNode.end); } } @@ -4316,6 +4342,8 @@ function forwardDebugInfoFromThenable( request: Request, task: Task, thenable: Thenable, + owner: null | ReactComponentInfo, // DEV-only + stack: null | Error, // DEV-only ): void { let debugInfo: ?ReactDebugInfo; if (__DEV__) { @@ -4332,7 +4360,7 @@ function forwardDebugInfoFromThenable( ) { const sequence = getAsyncSequenceFromPromise(thenable); if (sequence !== null) { - emitAsyncSequence(request, task, sequence, debugInfo); + emitAsyncSequence(request, task, sequence, debugInfo, owner, stack); } } } @@ -4357,7 +4385,7 @@ function forwardDebugInfoFromCurrentContext( ) { const sequence = getCurrentAsyncSequence(); if (sequence !== null) { - emitAsyncSequence(request, task, sequence, debugInfo); + emitAsyncSequence(request, task, sequence, debugInfo, null, null); } } } diff --git a/packages/react-server/src/ReactFlightThenable.js b/packages/react-server/src/ReactFlightThenable.js index 87fc2ea1fbfde..99ddb36fa5995 100644 --- a/packages/react-server/src/ReactFlightThenable.js +++ b/packages/react-server/src/ReactFlightThenable.js @@ -20,6 +20,8 @@ import type { RejectedThenable, } from 'shared/ReactTypes'; +import {enableAsyncDebugInfo} from 'shared/ReactFeatureFlags'; + import noop from 'shared/noop'; export type ThenableState = Array>; @@ -50,6 +52,11 @@ export function trackUsedThenable( const previous = thenableState[index]; if (previous === undefined) { thenableState.push(thenable); + if (__DEV__ && enableAsyncDebugInfo) { + const stacks: Array = + (thenableState: any)._stacks || ((thenableState: any)._stacks = []); + stacks.push(new Error()); + } } else { if (previous !== thenable) { // Reuse the previous thenable, and drop the new one. We can assume diff --git a/packages/react-server/src/__tests__/ReactFlightAsyncDebugInfo-test.js b/packages/react-server/src/__tests__/ReactFlightAsyncDebugInfo-test.js index 21f64ae3100b2..42fa56a836a2d 100644 --- a/packages/react-server/src/__tests__/ReactFlightAsyncDebugInfo-test.js +++ b/packages/react-server/src/__tests__/ReactFlightAsyncDebugInfo-test.js @@ -718,6 +718,32 @@ describe('ReactFlightAsyncDebugInfo', () => { "start": 0, }, "env": "Server", + "owner": { + "env": "Server", + "key": null, + "name": "InnerComponent", + "props": {}, + "stack": [ + [ + "Component", + "/packages/react-server/src/__tests__/ReactFlightAsyncDebugInfo-test.js", + 507, + 60, + 504, + 5, + ], + ], + }, + "stack": [ + [ + "InnerComponent", + "/packages/react-server/src/__tests__/ReactFlightAsyncDebugInfo-test.js", + 513, + 40, + 510, + 5, + ], + ], }, { "time": 0, @@ -775,9 +801,9 @@ describe('ReactFlightAsyncDebugInfo', () => { [ "Object.", "/packages/react-server/src/__tests__/ReactFlightAsyncDebugInfo-test.js", - 746, + 772, 109, - 733, + 759, 67, ], ], @@ -796,9 +822,9 @@ describe('ReactFlightAsyncDebugInfo', () => { [ "Object.", "/packages/react-server/src/__tests__/ReactFlightAsyncDebugInfo-test.js", - 746, + 772, 109, - 733, + 759, 67, ], ], @@ -807,9 +833,9 @@ describe('ReactFlightAsyncDebugInfo', () => { [ "Component", "/packages/react-server/src/__tests__/ReactFlightAsyncDebugInfo-test.js", - 736, + 762, 7, - 734, + 760, 5, ], ], @@ -869,9 +895,9 @@ describe('ReactFlightAsyncDebugInfo', () => { [ "Object.", "/packages/react-server/src/__tests__/ReactFlightAsyncDebugInfo-test.js", - 840, + 866, 109, - 831, + 857, 94, ], ], @@ -940,9 +966,9 @@ describe('ReactFlightAsyncDebugInfo', () => { [ "Object.", "/packages/react-server/src/__tests__/ReactFlightAsyncDebugInfo-test.js", - 911, + 937, 109, - 887, + 913, 50, ], ], @@ -1022,9 +1048,9 @@ describe('ReactFlightAsyncDebugInfo', () => { [ "Object.", "/packages/react-server/src/__tests__/ReactFlightAsyncDebugInfo-test.js", - 993, + 1019, 109, - 976, + 1002, 63, ], ], @@ -1049,9 +1075,9 @@ describe('ReactFlightAsyncDebugInfo', () => { [ "Component", "/packages/react-server/src/__tests__/ReactFlightAsyncDebugInfo-test.js", - 989, + 1015, 24, - 988, + 1014, 5, ], ], @@ -1081,9 +1107,9 @@ describe('ReactFlightAsyncDebugInfo', () => { [ "Component", "/packages/react-server/src/__tests__/ReactFlightAsyncDebugInfo-test.js", - 989, + 1015, 24, - 988, + 1014, 5, ], ], @@ -1100,17 +1126,17 @@ describe('ReactFlightAsyncDebugInfo', () => { [ "getData", "/packages/react-server/src/__tests__/ReactFlightAsyncDebugInfo-test.js", - 978, + 1004, 13, - 977, + 1003, 5, ], [ "ThirdPartyComponent", "/packages/react-server/src/__tests__/ReactFlightAsyncDebugInfo-test.js", - 984, + 1010, 24, - 983, + 1009, 5, ], ], @@ -1134,9 +1160,9 @@ describe('ReactFlightAsyncDebugInfo', () => { [ "Component", "/packages/react-server/src/__tests__/ReactFlightAsyncDebugInfo-test.js", - 989, + 1015, 24, - 988, + 1014, 5, ], ], @@ -1145,17 +1171,17 @@ describe('ReactFlightAsyncDebugInfo', () => { [ "getData", "/packages/react-server/src/__tests__/ReactFlightAsyncDebugInfo-test.js", - 978, + 1004, 13, - 977, + 1003, 5, ], [ "ThirdPartyComponent", "/packages/react-server/src/__tests__/ReactFlightAsyncDebugInfo-test.js", - 984, + 1010, 24, - 983, + 1009, 5, ], ], @@ -1188,9 +1214,9 @@ describe('ReactFlightAsyncDebugInfo', () => { [ "Component", "/packages/react-server/src/__tests__/ReactFlightAsyncDebugInfo-test.js", - 989, + 1015, 24, - 988, + 1014, 5, ], ], @@ -1207,17 +1233,17 @@ describe('ReactFlightAsyncDebugInfo', () => { [ "getData", "/packages/react-server/src/__tests__/ReactFlightAsyncDebugInfo-test.js", - 979, + 1005, 13, - 977, + 1003, 5, ], [ "ThirdPartyComponent", "/packages/react-server/src/__tests__/ReactFlightAsyncDebugInfo-test.js", - 984, + 1010, 18, - 983, + 1009, 5, ], ], @@ -1241,9 +1267,9 @@ describe('ReactFlightAsyncDebugInfo', () => { [ "Component", "/packages/react-server/src/__tests__/ReactFlightAsyncDebugInfo-test.js", - 989, + 1015, 24, - 988, + 1014, 5, ], ], @@ -1252,17 +1278,17 @@ describe('ReactFlightAsyncDebugInfo', () => { [ "getData", "/packages/react-server/src/__tests__/ReactFlightAsyncDebugInfo-test.js", - 979, + 1005, 13, - 977, + 1003, 5, ], [ "ThirdPartyComponent", "/packages/react-server/src/__tests__/ReactFlightAsyncDebugInfo-test.js", - 984, + 1010, 18, - 983, + 1009, 5, ], ], @@ -1335,9 +1361,9 @@ describe('ReactFlightAsyncDebugInfo', () => { [ "Object.", "/packages/react-server/src/__tests__/ReactFlightAsyncDebugInfo-test.js", - 1301, + 1327, 40, - 1284, + 1310, 62, ], ], @@ -1359,9 +1385,9 @@ describe('ReactFlightAsyncDebugInfo', () => { [ "Object.", "/packages/react-server/src/__tests__/ReactFlightAsyncDebugInfo-test.js", - 1301, + 1327, 40, - 1284, + 1310, 62, ], ], @@ -1378,17 +1404,17 @@ describe('ReactFlightAsyncDebugInfo', () => { [ "getData", "/packages/react-server/src/__tests__/ReactFlightAsyncDebugInfo-test.js", - 1286, + 1312, 13, - 1285, + 1311, 25, ], [ "Component", "/packages/react-server/src/__tests__/ReactFlightAsyncDebugInfo-test.js", - 1296, + 1322, 13, - 1295, + 1321, 5, ], ], @@ -1404,9 +1430,9 @@ describe('ReactFlightAsyncDebugInfo', () => { [ "Object.", "/packages/react-server/src/__tests__/ReactFlightAsyncDebugInfo-test.js", - 1301, + 1327, 40, - 1284, + 1310, 62, ], ], @@ -1415,17 +1441,17 @@ describe('ReactFlightAsyncDebugInfo', () => { [ "getData", "/packages/react-server/src/__tests__/ReactFlightAsyncDebugInfo-test.js", - 1286, + 1312, 13, - 1285, + 1311, 25, ], [ "Component", "/packages/react-server/src/__tests__/ReactFlightAsyncDebugInfo-test.js", - 1296, + 1322, 13, - 1295, + 1321, 5, ], ], @@ -1445,9 +1471,9 @@ describe('ReactFlightAsyncDebugInfo', () => { [ "Component", "/packages/react-server/src/__tests__/ReactFlightAsyncDebugInfo-test.js", - 1297, + 1323, 60, - 1295, + 1321, 5, ], ], @@ -1469,9 +1495,9 @@ describe('ReactFlightAsyncDebugInfo', () => { [ "Object.", "/packages/react-server/src/__tests__/ReactFlightAsyncDebugInfo-test.js", - 1301, + 1327, 40, - 1284, + 1310, 62, ], ], @@ -1488,17 +1514,17 @@ describe('ReactFlightAsyncDebugInfo', () => { [ "getData", "/packages/react-server/src/__tests__/ReactFlightAsyncDebugInfo-test.js", - 1286, + 1312, 13, - 1285, + 1311, 25, ], [ "Component", "/packages/react-server/src/__tests__/ReactFlightAsyncDebugInfo-test.js", - 1296, + 1322, 13, - 1295, + 1321, 5, ], ], @@ -1514,9 +1540,9 @@ describe('ReactFlightAsyncDebugInfo', () => { [ "Component", "/packages/react-server/src/__tests__/ReactFlightAsyncDebugInfo-test.js", - 1297, + 1323, 60, - 1295, + 1321, 5, ], ], @@ -1525,9 +1551,9 @@ describe('ReactFlightAsyncDebugInfo', () => { [ "Child", "/packages/react-server/src/__tests__/ReactFlightAsyncDebugInfo-test.js", - 1291, + 1317, 28, - 1290, + 1316, 5, ], ], @@ -1596,9 +1622,9 @@ describe('ReactFlightAsyncDebugInfo', () => { [ "Object.", "/packages/react-server/src/__tests__/ReactFlightAsyncDebugInfo-test.js", - 1562, + 1588, 40, - 1546, + 1572, 57, ], ], @@ -1620,9 +1646,9 @@ describe('ReactFlightAsyncDebugInfo', () => { [ "Object.", "/packages/react-server/src/__tests__/ReactFlightAsyncDebugInfo-test.js", - 1562, + 1588, 40, - 1546, + 1572, 57, ], ], @@ -1639,17 +1665,17 @@ describe('ReactFlightAsyncDebugInfo', () => { [ "getData", "/packages/react-server/src/__tests__/ReactFlightAsyncDebugInfo-test.js", - 1548, + 1574, 13, - 1547, + 1573, 25, ], [ "Component", "/packages/react-server/src/__tests__/ReactFlightAsyncDebugInfo-test.js", - 1557, + 1583, 23, - 1556, + 1582, 5, ], ], @@ -1665,9 +1691,9 @@ describe('ReactFlightAsyncDebugInfo', () => { [ "Object.", "/packages/react-server/src/__tests__/ReactFlightAsyncDebugInfo-test.js", - 1562, + 1588, 40, - 1546, + 1572, 57, ], ], @@ -1676,17 +1702,17 @@ describe('ReactFlightAsyncDebugInfo', () => { [ "getData", "/packages/react-server/src/__tests__/ReactFlightAsyncDebugInfo-test.js", - 1548, + 1574, 13, - 1547, + 1573, 25, ], [ "Component", "/packages/react-server/src/__tests__/ReactFlightAsyncDebugInfo-test.js", - 1557, + 1583, 23, - 1556, + 1582, 5, ], ], @@ -1706,9 +1732,9 @@ describe('ReactFlightAsyncDebugInfo', () => { [ "Component", "/packages/react-server/src/__tests__/ReactFlightAsyncDebugInfo-test.js", - 1558, + 1584, 60, - 1556, + 1582, 5, ], ], @@ -1727,9 +1753,9 @@ describe('ReactFlightAsyncDebugInfo', () => { [ "Object.", "/packages/react-server/src/__tests__/ReactFlightAsyncDebugInfo-test.js", - 1562, + 1588, 40, - 1546, + 1572, 57, ], ], @@ -1746,17 +1772,17 @@ describe('ReactFlightAsyncDebugInfo', () => { [ "getData", "/packages/react-server/src/__tests__/ReactFlightAsyncDebugInfo-test.js", - 1548, + 1574, 13, - 1547, + 1573, 25, ], [ "Component", "/packages/react-server/src/__tests__/ReactFlightAsyncDebugInfo-test.js", - 1557, + 1583, 23, - 1556, + 1582, 5, ], ], @@ -1830,9 +1856,9 @@ describe('ReactFlightAsyncDebugInfo', () => { [ "Object.", "/packages/react-server/src/__tests__/ReactFlightAsyncDebugInfo-test.js", - 1796, + 1822, 40, - 1778, + 1804, 80, ], ], @@ -1854,9 +1880,9 @@ describe('ReactFlightAsyncDebugInfo', () => { [ "Object.", "/packages/react-server/src/__tests__/ReactFlightAsyncDebugInfo-test.js", - 1796, + 1822, 40, - 1778, + 1804, 80, ], ], @@ -1873,17 +1899,17 @@ describe('ReactFlightAsyncDebugInfo', () => { [ "delayTrice", "/packages/react-server/src/__tests__/ReactFlightAsyncDebugInfo-test.js", - 1786, + 1812, 13, - 1784, + 1810, 5, ], [ "Bar", "/packages/react-server/src/__tests__/ReactFlightAsyncDebugInfo-test.js", - 1791, + 1817, 13, - 1790, + 1816, 5, ], ], @@ -1899,9 +1925,9 @@ describe('ReactFlightAsyncDebugInfo', () => { [ "Object.", "/packages/react-server/src/__tests__/ReactFlightAsyncDebugInfo-test.js", - 1796, + 1822, 40, - 1778, + 1804, 80, ], ], @@ -1910,17 +1936,17 @@ describe('ReactFlightAsyncDebugInfo', () => { [ "delayTrice", "/packages/react-server/src/__tests__/ReactFlightAsyncDebugInfo-test.js", - 1786, + 1812, 13, - 1784, + 1810, 5, ], [ "Bar", "/packages/react-server/src/__tests__/ReactFlightAsyncDebugInfo-test.js", - 1791, + 1817, 13, - 1790, + 1816, 5, ], ], @@ -1942,9 +1968,9 @@ describe('ReactFlightAsyncDebugInfo', () => { [ "Object.", "/packages/react-server/src/__tests__/ReactFlightAsyncDebugInfo-test.js", - 1796, + 1822, 40, - 1778, + 1804, 80, ], ], @@ -1961,25 +1987,25 @@ describe('ReactFlightAsyncDebugInfo', () => { [ "delayTwice", "/packages/react-server/src/__tests__/ReactFlightAsyncDebugInfo-test.js", - 1780, + 1806, 13, - 1779, + 1805, 5, ], [ "delayTrice", "/packages/react-server/src/__tests__/ReactFlightAsyncDebugInfo-test.js", - 1785, + 1811, 15, - 1784, + 1810, 5, ], [ "Bar", "/packages/react-server/src/__tests__/ReactFlightAsyncDebugInfo-test.js", - 1791, + 1817, 13, - 1790, + 1816, 5, ], ], @@ -1995,9 +2021,9 @@ describe('ReactFlightAsyncDebugInfo', () => { [ "Object.", "/packages/react-server/src/__tests__/ReactFlightAsyncDebugInfo-test.js", - 1796, + 1822, 40, - 1778, + 1804, 80, ], ], @@ -2006,25 +2032,25 @@ describe('ReactFlightAsyncDebugInfo', () => { [ "delayTwice", "/packages/react-server/src/__tests__/ReactFlightAsyncDebugInfo-test.js", - 1780, + 1806, 13, - 1779, + 1805, 5, ], [ "delayTrice", "/packages/react-server/src/__tests__/ReactFlightAsyncDebugInfo-test.js", - 1785, + 1811, 15, - 1784, + 1810, 5, ], [ "Bar", "/packages/react-server/src/__tests__/ReactFlightAsyncDebugInfo-test.js", - 1791, + 1817, 13, - 1790, + 1816, 5, ], ], @@ -2046,9 +2072,9 @@ describe('ReactFlightAsyncDebugInfo', () => { [ "Object.", "/packages/react-server/src/__tests__/ReactFlightAsyncDebugInfo-test.js", - 1796, + 1822, 40, - 1778, + 1804, 80, ], ], @@ -2065,9 +2091,9 @@ describe('ReactFlightAsyncDebugInfo', () => { [ "delayTwice", "/packages/react-server/src/__tests__/ReactFlightAsyncDebugInfo-test.js", - 1781, + 1807, 13, - 1779, + 1805, 5, ], ], @@ -2083,9 +2109,9 @@ describe('ReactFlightAsyncDebugInfo', () => { [ "Object.", "/packages/react-server/src/__tests__/ReactFlightAsyncDebugInfo-test.js", - 1796, + 1822, 40, - 1778, + 1804, 80, ], ], @@ -2094,9 +2120,9 @@ describe('ReactFlightAsyncDebugInfo', () => { [ "delayTwice", "/packages/react-server/src/__tests__/ReactFlightAsyncDebugInfo-test.js", - 1781, + 1807, 13, - 1779, + 1805, 5, ], ],