Skip to content

Commit 45da4e0

Browse files
authored
[Flight] Track Owner on AsyncInfo and IOInfo (#33395)
Stacked on #33394. This lets us create async stack traces to the owner that was in context when the I/O was started or awaited. <img width="615" alt="Screenshot 2025-06-01 at 12 31 52 AM" src="https://github.com/user-attachments/assets/6ff5a146-33d6-4a4b-84af-1b57e73047d4" /> This owner might not be the immediate closest parent where the I/O was awaited.
1 parent d8919a0 commit 45da4e0

File tree

6 files changed

+154
-33
lines changed

6 files changed

+154
-33
lines changed

packages/react-client/src/ReactFlightClient.js

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -2616,14 +2616,15 @@ function resolveDebugInfo(
26162616
initializeFakeTask(response, componentInfoOrAsyncInfo, env);
26172617
}
26182618
if (debugInfo.owner === null && response._debugRootOwner != null) {
2619-
// $FlowFixMe[prop-missing] By narrowing `owner` to `null`, we narrowed `debugInfo` to `ReactComponentInfo`
2620-
const componentInfo: ReactComponentInfo = debugInfo;
2619+
const componentInfoOrAsyncInfo: ReactComponentInfo | ReactAsyncInfo =
2620+
// $FlowFixMe: By narrowing `owner` to `null`, we narrowed `debugInfo` to `ReactComponentInfo`
2621+
debugInfo;
26212622
// $FlowFixMe[cannot-write]
2622-
componentInfo.owner = response._debugRootOwner;
2623+
componentInfoOrAsyncInfo.owner = response._debugRootOwner;
26232624
// We override the stack if we override the owner since the stack where the root JSX
26242625
// was created on the server isn't very useful but where the request was made is.
26252626
// $FlowFixMe[cannot-write]
2626-
componentInfo.debugStack = response._debugRootStack;
2627+
componentInfoOrAsyncInfo.debugStack = response._debugRootStack;
26272628
} else if (debugInfo.stack !== undefined) {
26282629
const componentInfoOrAsyncInfo: ReactComponentInfo | ReactAsyncInfo =
26292630
// $FlowFixMe[incompatible-type]
@@ -2764,7 +2765,6 @@ function initializeIOInfo(response: Response, ioInfo: ReactIOInfo): void {
27642765
initializeFakeTask(response, ioInfo, env);
27652766
initializeFakeStack(response, ioInfo);
27662767
}
2767-
// TODO: Initialize owner.
27682768
// Adjust the time to the current environment's time space.
27692769
// $FlowFixMe[cannot-write]
27702770
ioInfo.start += response._timeOrigin;

packages/react-server/src/ReactFlightAsyncSequence.js

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,12 +7,15 @@
77
* @flow
88
*/
99

10+
import type {ReactComponentInfo} from 'shared/ReactTypes';
11+
1012
export const IO_NODE = 0;
1113
export const PROMISE_NODE = 1;
1214
export const AWAIT_NODE = 2;
1315

1416
export type IONode = {
1517
tag: 0,
18+
owner: null | ReactComponentInfo,
1619
stack: Error, // callsite that spawned the I/O
1720
start: number, // start time when the first part of the I/O sequence started
1821
end: number, // we typically don't use this. only when there's no promise intermediate.
@@ -22,6 +25,7 @@ export type IONode = {
2225

2326
export type PromiseNode = {
2427
tag: 1,
28+
owner: null | ReactComponentInfo,
2529
stack: Error, // callsite that created the Promise
2630
start: number, // start time when the Promise was created
2731
end: number, // end time when the Promise was resolved.
@@ -31,6 +35,7 @@ export type PromiseNode = {
3135

3236
export type AwaitNode = {
3337
tag: 2,
38+
owner: null | ReactComponentInfo,
3439
stack: Error, // callsite that awaited (using await, .then(), Promise.all(), ...)
3540
start: -1.1, // not used. We use the timing of the awaited promise.
3641
end: -1.1, // not used.

packages/react-server/src/ReactFlightServer.js

Lines changed: 23 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1934,6 +1934,7 @@ function visitAsyncNode(
19341934
request.pendingChunks++;
19351935
emitDebugChunk(request, task.id, {
19361936
awaited: ((ioNode: any): ReactIOInfo), // This is deduped by this reference.
1937+
owner: node.owner,
19371938
stack: stack,
19381939
});
19391940
}
@@ -3523,6 +3524,7 @@ function emitIOInfoChunk(
35233524
name: string,
35243525
start: number,
35253526
end: number,
3527+
owner: ?ReactComponentInfo,
35263528
stack: ?ReactStackTrace,
35273529
): void {
35283530
if (!__DEV__) {
@@ -3560,8 +3562,15 @@ function emitIOInfoChunk(
35603562
name: name,
35613563
start: relativeStartTimestamp,
35623564
end: relativeEndTimestamp,
3563-
stack: stack,
35643565
};
3566+
if (stack != null) {
3567+
// $FlowFixMe[cannot-write]
3568+
debugIOInfo.stack = stack;
3569+
}
3570+
if (owner != null) {
3571+
// $FlowFixMe[cannot-write]
3572+
debugIOInfo.owner = owner;
3573+
}
35653574
// $FlowFixMe[incompatible-type] stringify can return null
35663575
const json: string = stringify(debugIOInfo, replacer);
35673576
const row = id.toString(16) + ':J' + json + '\n';
@@ -3577,12 +3586,18 @@ function outlineIOInfo(request: Request, ioInfo: ReactIOInfo): void {
35773586
// We can't serialize the ConsoleTask/Error objects so we need to omit them before serializing.
35783587
request.pendingChunks++;
35793588
const id = request.nextChunkId++;
3589+
const owner = ioInfo.owner;
3590+
// Ensure the owner is already outlined.
3591+
if (owner != null) {
3592+
outlineComponentInfo(request, owner);
3593+
}
35803594
emitIOInfoChunk(
35813595
request,
35823596
id,
35833597
ioInfo.name,
35843598
ioInfo.start,
35853599
ioInfo.end,
3600+
owner,
35863601
ioInfo.stack,
35873602
);
35883603
request.writtenObjects.set(ioInfo, serializeByValueID(id));
@@ -3612,10 +3627,15 @@ function serializeIONode(
36123627
name = name.slice(7);
36133628
}
36143629
}
3630+
const owner = ioNode.owner;
3631+
// Ensure the owner is already outlined.
3632+
if (owner != null) {
3633+
outlineComponentInfo(request, owner);
3634+
}
36153635

36163636
request.pendingChunks++;
36173637
const id = request.nextChunkId++;
3618-
emitIOInfoChunk(request, id, name, ioNode.start, ioNode.end, stack);
3638+
emitIOInfoChunk(request, id, name, ioNode.start, ioNode.end, owner, stack);
36193639
const ref = serializeByValueID(id);
36203640
request.writtenObjects.set(ioNode, ref);
36213641
return ref;
@@ -4141,6 +4161,7 @@ function forwardDebugInfo(
41414161
const debugAsyncInfo: Omit<ReactAsyncInfo, 'debugTask' | 'debugStack'> =
41424162
{
41434163
awaited: ioInfo,
4164+
owner: debugInfo[i].owner,
41444165
stack: debugInfo[i].stack,
41454166
};
41464167
emitDebugChunk(request, id, debugAsyncInfo);

packages/react-server/src/ReactFlightServerConfigDebugNode.js

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@ import type {
1515
} from './ReactFlightAsyncSequence';
1616

1717
import {IO_NODE, PROMISE_NODE, AWAIT_NODE} from './ReactFlightAsyncSequence';
18+
import {resolveOwner} from './flight/ReactFlightCurrentOwner';
1819
import {createHook, executionAsyncId} from 'async_hooks';
1920
import {enableAsyncDebugInfo} from 'shared/ReactFeatureFlags';
2021

@@ -46,6 +47,7 @@ export function initAsyncDebugInfo(): void {
4647
// so that we can later pick the best stack trace in user space.
4748
node = ({
4849
tag: AWAIT_NODE,
50+
owner: resolveOwner(),
4951
stack: new Error(),
5052
start: -1.1,
5153
end: -1.1,
@@ -55,6 +57,7 @@ export function initAsyncDebugInfo(): void {
5557
} else {
5658
node = ({
5759
tag: PROMISE_NODE,
60+
owner: resolveOwner(),
5861
stack: new Error(),
5962
start: performance.now(),
6063
end: -1.1, // Set when we resolve.
@@ -74,6 +77,7 @@ export function initAsyncDebugInfo(): void {
7477
// We have begun a new I/O sequence.
7578
node = ({
7679
tag: IO_NODE,
80+
owner: resolveOwner(),
7781
stack: new Error(), // This is only used if no native promises are used.
7882
start: performance.now(),
7983
end: -1.1, // Only set when pinged.
@@ -84,6 +88,7 @@ export function initAsyncDebugInfo(): void {
8488
// We have begun a new I/O sequence after the await.
8589
node = ({
8690
tag: IO_NODE,
91+
owner: resolveOwner(),
8792
stack: new Error(),
8893
start: performance.now(),
8994
end: -1.1, // Only set when pinged.

0 commit comments

Comments
 (0)