[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.
Sebastian Markbåge committed
Jul 8, 2025 at 13:02 UTC
a7a116577daf3b135c226ed9db8a8c2f9166c023
2 files changed
+10
-7
packages/react-server/src/ReactFlightAsyncSequence.js
+1
-1
@@ -26,7 +26,7 @@ type PromiseWithDebugInfo = interface extends Promise<any> {
26
export type IONode = {
27
tag: 0,
28
owner: null | ReactComponentInfo,
29
- stack: null, // callsite that spawned the I/O
29
+ stack: null | ReactStackTrace, // callsite that spawned the I/O
30
start: number, // start time when the first part of the I/O sequence started
31
end: number, // we typically don't use this. only when there's no promise intermediate.
32
promise: null, // not used on I/O
packages/react-server/src/ReactFlightServerConfigDebugNode.js
+9
-6
@@ -149,10 +149,11 @@ export function initAsyncDebugInfo(): void {
149
previous: current === undefined ? null : current, // The path that led us here.
150
}: UnresolvedAwaitNode);
151
} else {
152
+ const owner = resolveOwner();
153
node = ({
154
tag: UNRESOLVED_PROMISE_NODE,
154
- owner: resolveOwner(),
155
- stack: parseStackTrace(new Error(), 5),
155
+ owner: owner,
156
+ stack: owner === null ? null : parseStackTrace(new Error(), 5),
157
start: performance.now(),
158
end: -1.1, // Set when we resolve.
159
promise: new WeakRef((resource: Promise<any>)),
@@ -170,10 +171,11 @@ export function initAsyncDebugInfo(): void {
171
) {
172
if (trigger === undefined) {
173
// We have begun a new I/O sequence.
174
+ const owner = resolveOwner();
175
node = ({
176
tag: IO_NODE,
175
- owner: resolveOwner(),
176
- stack: null,
177
+ owner: owner,
178
+ stack: owner === null ? parseStackTrace(new Error(), 3) : null,
179
start: performance.now(),
180
end: -1.1, // Only set when pinged.
181
promise: null,
@@ -185,10 +187,11 @@ export function initAsyncDebugInfo(): void {
187
trigger.tag === UNRESOLVED_AWAIT_NODE
188
) {
189
// We have begun a new I/O sequence after the await.
190
+ const owner = resolveOwner();
191
node = ({
192
tag: IO_NODE,
190
- owner: resolveOwner(),
191
- stack: null,
193
+ owner: owner,
194
+ stack: owner === null ? parseStackTrace(new Error(), 3) : null,
195
start: performance.now(),
196
end: -1.1, // Only set when pinged.
197
promise: null,