Files
react/packages/react-server/src/ReactFlightAsyncSequence.js
Sebastian Markbåge a7a116577d [Flight] Don't track Promise stack if there's no owner (#33734)
This is a compromise because there can be a lot of Promise instances
created. They're useful because they generally provide a better stack
when batching/pooled connections are used.

This restores stack collection for I/O nodes so we have something to
fallback on if there's no owner.

That way we can at least get a name or something out of I/O that was
spawned outside a render but mostly avoids collecting starting I/O
outside of render.
2025-07-08 13:02:29 -04:00

87 lines
3.4 KiB
JavaScript

/**
* Copyright (c) Meta Platforms, Inc. and affiliates.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*
* @flow
*/
import type {
ReactDebugInfo,
ReactComponentInfo,
ReactStackTrace,
} from 'shared/ReactTypes';
export const IO_NODE = 0;
export const PROMISE_NODE = 1;
export const AWAIT_NODE = 2;
export const UNRESOLVED_PROMISE_NODE = 3;
export const UNRESOLVED_AWAIT_NODE = 4;
type PromiseWithDebugInfo = interface extends Promise<any> {
_debugInfo?: ReactDebugInfo,
};
export type IONode = {
tag: 0,
owner: null | ReactComponentInfo,
stack: null | ReactStackTrace, // 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.
promise: null, // not used on I/O
awaited: null, // I/O is only blocked on external.
previous: null | AwaitNode | UnresolvedAwaitNode, // the preceeding await that spawned this new work
};
export type PromiseNode = {
tag: 1,
owner: null | ReactComponentInfo,
stack: null | ReactStackTrace, // callsite that created the Promise
start: number, // start time when the Promise was created
end: number, // end time when the Promise was resolved.
promise: WeakRef<PromiseWithDebugInfo>, // a reference to this Promise if still referenced
awaited: null | AsyncSequence, // the thing that ended up resolving this promise
previous: null | AsyncSequence, // represents what the last return of an async function depended on before returning
};
export type AwaitNode = {
tag: 2,
owner: null | ReactComponentInfo,
stack: null | ReactStackTrace, // callsite that awaited (using await, .then(), Promise.all(), ...)
start: number, // when we started blocking. This might be later than the I/O started.
end: number, // when we unblocked. This might be later than the I/O resolved if there's CPU time.
promise: WeakRef<PromiseWithDebugInfo>, // a reference to this Promise if still referenced
awaited: null | AsyncSequence, // the promise we were waiting on
previous: null | AsyncSequence, // the sequence that was blocking us from awaiting in the first place
};
export type UnresolvedPromiseNode = {
tag: 3,
owner: null | ReactComponentInfo,
stack: null | ReactStackTrace, // callsite that created the Promise
start: number, // start time when the Promise was created
end: -1.1, // set when we resolve.
promise: WeakRef<PromiseWithDebugInfo>, // a reference to this Promise if still referenced
awaited: null | AsyncSequence, // the thing that ended up resolving this promise
previous: null, // where we created the promise is not interesting since creating it doesn't mean waiting.
};
export type UnresolvedAwaitNode = {
tag: 4,
owner: null | ReactComponentInfo,
stack: null | ReactStackTrace, // callsite that awaited (using await, .then(), Promise.all(), ...)
start: number, // when we started blocking. This might be later than the I/O started.
end: -1.1, // set when we resolve.
promise: WeakRef<PromiseWithDebugInfo>, // a reference to this Promise if still referenced
awaited: null | AsyncSequence, // the promise we were waiting on
previous: null | AsyncSequence, // the sequence that was blocking us from awaiting in the first place
};
export type AsyncSequence =
| IONode
| PromiseNode
| AwaitNode
| UnresolvedPromiseNode
| UnresolvedAwaitNode;