mirror of
https://github.com/facebook/react.git
synced 2025-11-01 09:12:30 +00:00
[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.
This commit is contained in:
committed by
GitHub
parent
d8919a0a68
commit
45da4e055d
+5
-5
@@ -2616,14 +2616,15 @@ function resolveDebugInfo(
|
||||
initializeFakeTask(response, componentInfoOrAsyncInfo, env);
|
||||
}
|
||||
if (debugInfo.owner === null && response._debugRootOwner != null) {
|
||||
// $FlowFixMe[prop-missing] By narrowing `owner` to `null`, we narrowed `debugInfo` to `ReactComponentInfo`
|
||||
const componentInfo: ReactComponentInfo = debugInfo;
|
||||
const componentInfoOrAsyncInfo: ReactComponentInfo | ReactAsyncInfo =
|
||||
// $FlowFixMe: By narrowing `owner` to `null`, we narrowed `debugInfo` to `ReactComponentInfo`
|
||||
debugInfo;
|
||||
// $FlowFixMe[cannot-write]
|
||||
componentInfo.owner = response._debugRootOwner;
|
||||
componentInfoOrAsyncInfo.owner = response._debugRootOwner;
|
||||
// We override the stack if we override the owner since the stack where the root JSX
|
||||
// was created on the server isn't very useful but where the request was made is.
|
||||
// $FlowFixMe[cannot-write]
|
||||
componentInfo.debugStack = response._debugRootStack;
|
||||
componentInfoOrAsyncInfo.debugStack = response._debugRootStack;
|
||||
} else if (debugInfo.stack !== undefined) {
|
||||
const componentInfoOrAsyncInfo: ReactComponentInfo | ReactAsyncInfo =
|
||||
// $FlowFixMe[incompatible-type]
|
||||
@@ -2764,7 +2765,6 @@ function initializeIOInfo(response: Response, ioInfo: ReactIOInfo): void {
|
||||
initializeFakeTask(response, ioInfo, env);
|
||||
initializeFakeStack(response, ioInfo);
|
||||
}
|
||||
// TODO: Initialize owner.
|
||||
// Adjust the time to the current environment's time space.
|
||||
// $FlowFixMe[cannot-write]
|
||||
ioInfo.start += response._timeOrigin;
|
||||
|
||||
@@ -7,12 +7,15 @@
|
||||
* @flow
|
||||
*/
|
||||
|
||||
import type {ReactComponentInfo} from 'shared/ReactTypes';
|
||||
|
||||
export const IO_NODE = 0;
|
||||
export const PROMISE_NODE = 1;
|
||||
export const AWAIT_NODE = 2;
|
||||
|
||||
export type IONode = {
|
||||
tag: 0,
|
||||
owner: null | ReactComponentInfo,
|
||||
stack: Error, // callsite that spawned the I/O
|
||||
start: number, // start time when the first part of the I/O sequence started
|
||||
end: number, // we typically don't use this. only when there's no promise intermediate.
|
||||
@@ -22,6 +25,7 @@ export type IONode = {
|
||||
|
||||
export type PromiseNode = {
|
||||
tag: 1,
|
||||
owner: null | ReactComponentInfo,
|
||||
stack: Error, // callsite that created the Promise
|
||||
start: number, // start time when the Promise was created
|
||||
end: number, // end time when the Promise was resolved.
|
||||
@@ -31,6 +35,7 @@ export type PromiseNode = {
|
||||
|
||||
export type AwaitNode = {
|
||||
tag: 2,
|
||||
owner: null | ReactComponentInfo,
|
||||
stack: Error, // callsite that awaited (using await, .then(), Promise.all(), ...)
|
||||
start: -1.1, // not used. We use the timing of the awaited promise.
|
||||
end: -1.1, // not used.
|
||||
|
||||
+23
-2
@@ -1934,6 +1934,7 @@ function visitAsyncNode(
|
||||
request.pendingChunks++;
|
||||
emitDebugChunk(request, task.id, {
|
||||
awaited: ((ioNode: any): ReactIOInfo), // This is deduped by this reference.
|
||||
owner: node.owner,
|
||||
stack: stack,
|
||||
});
|
||||
}
|
||||
@@ -3523,6 +3524,7 @@ function emitIOInfoChunk(
|
||||
name: string,
|
||||
start: number,
|
||||
end: number,
|
||||
owner: ?ReactComponentInfo,
|
||||
stack: ?ReactStackTrace,
|
||||
): void {
|
||||
if (!__DEV__) {
|
||||
@@ -3560,8 +3562,15 @@ function emitIOInfoChunk(
|
||||
name: name,
|
||||
start: relativeStartTimestamp,
|
||||
end: relativeEndTimestamp,
|
||||
stack: stack,
|
||||
};
|
||||
if (stack != null) {
|
||||
// $FlowFixMe[cannot-write]
|
||||
debugIOInfo.stack = stack;
|
||||
}
|
||||
if (owner != null) {
|
||||
// $FlowFixMe[cannot-write]
|
||||
debugIOInfo.owner = owner;
|
||||
}
|
||||
// $FlowFixMe[incompatible-type] stringify can return null
|
||||
const json: string = stringify(debugIOInfo, replacer);
|
||||
const row = id.toString(16) + ':J' + json + '\n';
|
||||
@@ -3577,12 +3586,18 @@ function outlineIOInfo(request: Request, ioInfo: ReactIOInfo): void {
|
||||
// We can't serialize the ConsoleTask/Error objects so we need to omit them before serializing.
|
||||
request.pendingChunks++;
|
||||
const id = request.nextChunkId++;
|
||||
const owner = ioInfo.owner;
|
||||
// Ensure the owner is already outlined.
|
||||
if (owner != null) {
|
||||
outlineComponentInfo(request, owner);
|
||||
}
|
||||
emitIOInfoChunk(
|
||||
request,
|
||||
id,
|
||||
ioInfo.name,
|
||||
ioInfo.start,
|
||||
ioInfo.end,
|
||||
owner,
|
||||
ioInfo.stack,
|
||||
);
|
||||
request.writtenObjects.set(ioInfo, serializeByValueID(id));
|
||||
@@ -3612,10 +3627,15 @@ function serializeIONode(
|
||||
name = name.slice(7);
|
||||
}
|
||||
}
|
||||
const owner = ioNode.owner;
|
||||
// Ensure the owner is already outlined.
|
||||
if (owner != null) {
|
||||
outlineComponentInfo(request, owner);
|
||||
}
|
||||
|
||||
request.pendingChunks++;
|
||||
const id = request.nextChunkId++;
|
||||
emitIOInfoChunk(request, id, name, ioNode.start, ioNode.end, stack);
|
||||
emitIOInfoChunk(request, id, name, ioNode.start, ioNode.end, owner, stack);
|
||||
const ref = serializeByValueID(id);
|
||||
request.writtenObjects.set(ioNode, ref);
|
||||
return ref;
|
||||
@@ -4141,6 +4161,7 @@ function forwardDebugInfo(
|
||||
const debugAsyncInfo: Omit<ReactAsyncInfo, 'debugTask' | 'debugStack'> =
|
||||
{
|
||||
awaited: ioInfo,
|
||||
owner: debugInfo[i].owner,
|
||||
stack: debugInfo[i].stack,
|
||||
};
|
||||
emitDebugChunk(request, id, debugAsyncInfo);
|
||||
|
||||
@@ -15,6 +15,7 @@ import type {
|
||||
} from './ReactFlightAsyncSequence';
|
||||
|
||||
import {IO_NODE, PROMISE_NODE, AWAIT_NODE} from './ReactFlightAsyncSequence';
|
||||
import {resolveOwner} from './flight/ReactFlightCurrentOwner';
|
||||
import {createHook, executionAsyncId} from 'async_hooks';
|
||||
import {enableAsyncDebugInfo} from 'shared/ReactFeatureFlags';
|
||||
|
||||
@@ -46,6 +47,7 @@ export function initAsyncDebugInfo(): void {
|
||||
// so that we can later pick the best stack trace in user space.
|
||||
node = ({
|
||||
tag: AWAIT_NODE,
|
||||
owner: resolveOwner(),
|
||||
stack: new Error(),
|
||||
start: -1.1,
|
||||
end: -1.1,
|
||||
@@ -55,6 +57,7 @@ export function initAsyncDebugInfo(): void {
|
||||
} else {
|
||||
node = ({
|
||||
tag: PROMISE_NODE,
|
||||
owner: resolveOwner(),
|
||||
stack: new Error(),
|
||||
start: performance.now(),
|
||||
end: -1.1, // Set when we resolve.
|
||||
@@ -74,6 +77,7 @@ export function initAsyncDebugInfo(): void {
|
||||
// We have begun a new I/O sequence.
|
||||
node = ({
|
||||
tag: IO_NODE,
|
||||
owner: resolveOwner(),
|
||||
stack: new Error(), // This is only used if no native promises are used.
|
||||
start: performance.now(),
|
||||
end: -1.1, // Only set when pinged.
|
||||
@@ -84,6 +88,7 @@ export function initAsyncDebugInfo(): void {
|
||||
// We have begun a new I/O sequence after the await.
|
||||
node = ({
|
||||
tag: IO_NODE,
|
||||
owner: resolveOwner(),
|
||||
stack: new Error(),
|
||||
start: performance.now(),
|
||||
end: -1.1, // Only set when pinged.
|
||||
|
||||
@@ -39,6 +39,9 @@ function normalizeIOInfo(ioInfo) {
|
||||
if (ioInfo.stack) {
|
||||
copy.stack = normalizeStack(ioInfo.stack);
|
||||
}
|
||||
if (ioInfo.owner) {
|
||||
copy.owner = normalizeDebugInfo(ioInfo.owner);
|
||||
}
|
||||
if (typeof ioInfo.start === 'number') {
|
||||
copy.start = 0;
|
||||
}
|
||||
@@ -160,9 +163,9 @@ describe('ReactFlightAsyncDebugInfo', () => {
|
||||
[
|
||||
"Object.<anonymous>",
|
||||
"/packages/react-server/src/__tests__/ReactFlightAsyncDebugInfo-test.js",
|
||||
130,
|
||||
133,
|
||||
109,
|
||||
117,
|
||||
120,
|
||||
50,
|
||||
],
|
||||
],
|
||||
@@ -171,49 +174,83 @@ describe('ReactFlightAsyncDebugInfo', () => {
|
||||
"awaited": {
|
||||
"end": 0,
|
||||
"name": "delay",
|
||||
"owner": {
|
||||
"env": "Server",
|
||||
"key": null,
|
||||
"name": "Component",
|
||||
"owner": null,
|
||||
"props": {},
|
||||
"stack": [
|
||||
[
|
||||
"Object.<anonymous>",
|
||||
"/packages/react-server/src/__tests__/ReactFlightAsyncDebugInfo-test.js",
|
||||
133,
|
||||
109,
|
||||
120,
|
||||
50,
|
||||
],
|
||||
],
|
||||
},
|
||||
"stack": [
|
||||
[
|
||||
"delay",
|
||||
"/packages/react-server/src/__tests__/ReactFlightAsyncDebugInfo-test.js",
|
||||
112,
|
||||
115,
|
||||
12,
|
||||
111,
|
||||
114,
|
||||
3,
|
||||
],
|
||||
[
|
||||
"getData",
|
||||
"/packages/react-server/src/__tests__/ReactFlightAsyncDebugInfo-test.js",
|
||||
119,
|
||||
122,
|
||||
13,
|
||||
118,
|
||||
121,
|
||||
5,
|
||||
],
|
||||
[
|
||||
"Component",
|
||||
"/packages/react-server/src/__tests__/ReactFlightAsyncDebugInfo-test.js",
|
||||
126,
|
||||
129,
|
||||
26,
|
||||
125,
|
||||
128,
|
||||
5,
|
||||
],
|
||||
],
|
||||
"start": 0,
|
||||
},
|
||||
"owner": {
|
||||
"env": "Server",
|
||||
"key": null,
|
||||
"name": "Component",
|
||||
"owner": null,
|
||||
"props": {},
|
||||
"stack": [
|
||||
[
|
||||
"Object.<anonymous>",
|
||||
"/packages/react-server/src/__tests__/ReactFlightAsyncDebugInfo-test.js",
|
||||
133,
|
||||
109,
|
||||
120,
|
||||
50,
|
||||
],
|
||||
],
|
||||
},
|
||||
"stack": [
|
||||
[
|
||||
"getData",
|
||||
"/packages/react-server/src/__tests__/ReactFlightAsyncDebugInfo-test.js",
|
||||
119,
|
||||
122,
|
||||
13,
|
||||
118,
|
||||
121,
|
||||
5,
|
||||
],
|
||||
[
|
||||
"Component",
|
||||
"/packages/react-server/src/__tests__/ReactFlightAsyncDebugInfo-test.js",
|
||||
126,
|
||||
129,
|
||||
26,
|
||||
125,
|
||||
128,
|
||||
5,
|
||||
],
|
||||
],
|
||||
@@ -222,49 +259,83 @@ describe('ReactFlightAsyncDebugInfo', () => {
|
||||
"awaited": {
|
||||
"end": 0,
|
||||
"name": "delay",
|
||||
"owner": {
|
||||
"env": "Server",
|
||||
"key": null,
|
||||
"name": "Component",
|
||||
"owner": null,
|
||||
"props": {},
|
||||
"stack": [
|
||||
[
|
||||
"Object.<anonymous>",
|
||||
"/packages/react-server/src/__tests__/ReactFlightAsyncDebugInfo-test.js",
|
||||
133,
|
||||
109,
|
||||
120,
|
||||
50,
|
||||
],
|
||||
],
|
||||
},
|
||||
"stack": [
|
||||
[
|
||||
"delay",
|
||||
"/packages/react-server/src/__tests__/ReactFlightAsyncDebugInfo-test.js",
|
||||
112,
|
||||
115,
|
||||
12,
|
||||
111,
|
||||
114,
|
||||
3,
|
||||
],
|
||||
[
|
||||
"getData",
|
||||
"/packages/react-server/src/__tests__/ReactFlightAsyncDebugInfo-test.js",
|
||||
120,
|
||||
123,
|
||||
21,
|
||||
118,
|
||||
121,
|
||||
5,
|
||||
],
|
||||
[
|
||||
"Component",
|
||||
"/packages/react-server/src/__tests__/ReactFlightAsyncDebugInfo-test.js",
|
||||
126,
|
||||
129,
|
||||
20,
|
||||
125,
|
||||
128,
|
||||
5,
|
||||
],
|
||||
],
|
||||
"start": 0,
|
||||
},
|
||||
"owner": {
|
||||
"env": "Server",
|
||||
"key": null,
|
||||
"name": "Component",
|
||||
"owner": null,
|
||||
"props": {},
|
||||
"stack": [
|
||||
[
|
||||
"Object.<anonymous>",
|
||||
"/packages/react-server/src/__tests__/ReactFlightAsyncDebugInfo-test.js",
|
||||
133,
|
||||
109,
|
||||
120,
|
||||
50,
|
||||
],
|
||||
],
|
||||
},
|
||||
"stack": [
|
||||
[
|
||||
"getData",
|
||||
"/packages/react-server/src/__tests__/ReactFlightAsyncDebugInfo-test.js",
|
||||
121,
|
||||
124,
|
||||
21,
|
||||
118,
|
||||
121,
|
||||
5,
|
||||
],
|
||||
[
|
||||
"Component",
|
||||
"/packages/react-server/src/__tests__/ReactFlightAsyncDebugInfo-test.js",
|
||||
126,
|
||||
129,
|
||||
20,
|
||||
125,
|
||||
128,
|
||||
5,
|
||||
],
|
||||
],
|
||||
@@ -323,9 +394,9 @@ describe('ReactFlightAsyncDebugInfo', () => {
|
||||
[
|
||||
"Object.<anonymous>",
|
||||
"/packages/react-server/src/__tests__/ReactFlightAsyncDebugInfo-test.js",
|
||||
293,
|
||||
364,
|
||||
109,
|
||||
280,
|
||||
351,
|
||||
67,
|
||||
],
|
||||
],
|
||||
@@ -334,13 +405,30 @@ describe('ReactFlightAsyncDebugInfo', () => {
|
||||
"awaited": {
|
||||
"end": 0,
|
||||
"name": "setTimeout",
|
||||
"owner": {
|
||||
"env": "Server",
|
||||
"key": null,
|
||||
"name": "Component",
|
||||
"owner": null,
|
||||
"props": {},
|
||||
"stack": [
|
||||
[
|
||||
"Object.<anonymous>",
|
||||
"/packages/react-server/src/__tests__/ReactFlightAsyncDebugInfo-test.js",
|
||||
364,
|
||||
109,
|
||||
351,
|
||||
67,
|
||||
],
|
||||
],
|
||||
},
|
||||
"stack": [
|
||||
[
|
||||
"Component",
|
||||
"/packages/react-server/src/__tests__/ReactFlightAsyncDebugInfo-test.js",
|
||||
283,
|
||||
354,
|
||||
7,
|
||||
281,
|
||||
352,
|
||||
5,
|
||||
],
|
||||
],
|
||||
|
||||
@@ -234,6 +234,7 @@ export type ReactIOInfo = {
|
||||
+name: string, // the name of the async function being called (e.g. "fetch")
|
||||
+start: number, // the start time
|
||||
+end: number, // the end time (this might be different from the time the await was unblocked)
|
||||
+owner?: null | ReactComponentInfo,
|
||||
+stack?: null | ReactStackTrace,
|
||||
// Stashed Data for the Specific Execution Environment. Not part of the transport protocol
|
||||
+debugStack?: null | Error,
|
||||
@@ -242,6 +243,7 @@ export type ReactIOInfo = {
|
||||
|
||||
export type ReactAsyncInfo = {
|
||||
+awaited: ReactIOInfo,
|
||||
+owner?: null | ReactComponentInfo,
|
||||
+stack?: null | ReactStackTrace,
|
||||
// Stashed Data for the Specific Execution Environment. Not part of the transport protocol
|
||||
+debugStack?: null | Error,
|
||||
|
||||
Reference in New Issue
Block a user