@samitouri / QOS-React-1 / commits / 157ac578de

[Flight] Include env in ReactAsyncInfo and ReactIOInfo (#33400)

Stacked on #33395. This lets us keep track of which environment this was fetched and awaited. Currently the IO and await is in the same environment. It's just kept when forwarded. Once we support forwarding information from a Promise fetched from another environment and awaited in this environment then the await can end up being in a different environment. There's a question of when the await is inside Flight itself such as when you return a promise fetched from another environment whether that should mean that the await is in the current environment. I don't think so since the original stack trace is the best stack trace. It's only if you `await` it in user space in this environment first that this might happen and even then it should only be considered if there wasn't a better await earlier or if reading from the other environment was itself I/O. The timing of *when* we read `environmentName()` is a little interesting here too.

Sebastian Markbåge committed Jun 3, 2025 at 17:28 UTC 157ac578ded11352330dbdfb8cf339b28c6a16d6
5 files changed +52 -14
packages/react-client/src/ReactFlightClient.js
+2 -4
@@ -2758,9 +2758,7 @@ function resolveConsoleEntry(
2758
2759 function initializeIOInfo(response: Response, ioInfo: ReactIOInfo): void {
2760 const env =
2761 - // TODO: Pass env through I/O info.
2762 - // ioInfo.env !== undefined ? ioInfo.env :
2763 - response._rootEnvironmentName;
2761 + ioInfo.env === undefined ? response._rootEnvironmentName : ioInfo.env;
2762 if (ioInfo.stack !== undefined) {
2763 initializeFakeTask(response, ioInfo, env);
2764 initializeFakeStack(response, ioInfo);
@@ -2771,7 +2769,7 @@ function initializeIOInfo(response: Response, ioInfo: ReactIOInfo): void {
2769 // $FlowFixMe[cannot-write]
2770 ioInfo.end += response._timeOrigin;
2771
2774 - logIOInfo(ioInfo);
2772 + logIOInfo(ioInfo, response._rootEnvironmentName);
2773 }
2774
2775 function resolveIOInfo(
packages/react-client/src/ReactFlightPerformanceTrack.js
+7 -3
@@ -224,11 +224,15 @@ function getIOColor(
224 }
225 }
226
227 -export function logIOInfo(ioInfo: ReactIOInfo): void {
227 +export function logIOInfo(ioInfo: ReactIOInfo, rootEnv: string): void {
228 const startTime = ioInfo.start;
229 const endTime = ioInfo.end;
230 if (supportsUserTiming && endTime >= 0) {
231 const name = ioInfo.name;
232 + const env = ioInfo.env;
233 + const isPrimaryEnv = env === rootEnv;
234 + const entryName =
235 + isPrimaryEnv || env === undefined ? name : name + ' [' + env + ']';
236 const debugTask = ioInfo.debugTask;
237 const color = getIOColor(name);
238 if (__DEV__ && debugTask) {
@@ -236,7 +240,7 @@ export function logIOInfo(ioInfo: ReactIOInfo): void {
240 // $FlowFixMe[method-unbinding]
241 console.timeStamp.bind(
242 console,
239 - name,
243 + entryName,
244 startTime < 0 ? 0 : startTime,
245 endTime,
246 IO_TRACK,
@@ -246,7 +250,7 @@ export function logIOInfo(ioInfo: ReactIOInfo): void {
250 );
251 } else {
252 console.timeStamp(
249 - name,
253 + entryName,
254 startTime < 0 ? 0 : startTime,
255 endTime,
256 IO_TRACK,
packages/react-server/src/ReactFlightServer.js
+29 -1
@@ -1930,10 +1930,14 @@ function visitAsyncNode(
1930 }
1931 // Outline the IO node.
1932 serializeIONode(request, ioNode);
1933 + // We log the environment at the time when the last promise pigned ping which may
1934 + // be later than what the environment was when we actually started awaiting.
1935 + const env = (0, request.environmentName)();
1936 // Then emit a reference to us awaiting it in the current task.
1937 request.pendingChunks++;
1938 emitDebugChunk(request, task.id, {
1939 awaited: ((ioNode: any): ReactIOInfo), // This is deduped by this reference.
1940 + env: env,
1941 owner: node.owner,
1942 stack: stack,
1943 });
@@ -1969,8 +1973,12 @@ function emitAsyncSequence(
1973 }
1974 serializeIONode(request, awaitedNode);
1975 request.pendingChunks++;
1976 + // We log the environment at the time when we ping which may be later than what the
1977 + // environment was when we actually started awaiting.
1978 + const env = (0, request.environmentName)();
1979 emitDebugChunk(request, task.id, {
1980 awaited: ((awaitedNode: any): ReactIOInfo), // This is deduped by this reference.
1981 + env: env,
1982 });
1983 }
1984 }
@@ -3524,6 +3532,7 @@ function emitIOInfoChunk(
3532 name: string,
3533 start: number,
3534 end: number,
3535 + env: ?string,
3536 owner: ?ReactComponentInfo,
3537 stack: ?ReactStackTrace,
3538 ): void {
@@ -3563,6 +3572,10 @@ function emitIOInfoChunk(
3572 start: relativeStartTimestamp,
3573 end: relativeEndTimestamp,
3574 };
3575 + if (env != null) {
3576 + // $FlowFixMe[cannot-write]
3577 + debugIOInfo.env = env;
3578 + }
3579 if (stack != null) {
3580 // $FlowFixMe[cannot-write]
3581 debugIOInfo.stack = stack;
@@ -3597,6 +3610,7 @@ function outlineIOInfo(request: Request, ioInfo: ReactIOInfo): void {
3610 ioInfo.name,
3611 ioInfo.start,
3612 ioInfo.end,
3613 + ioInfo.env,
3614 owner,
3615 ioInfo.stack,
3616 );
@@ -3633,9 +3647,22 @@ function serializeIONode(
3647 outlineComponentInfo(request, owner);
3648 }
3649
3650 + // We log the environment at the time when we serialize the I/O node.
3651 + // The environment name may have changed from when the I/O was actually started.
3652 + const env = (0, request.environmentName)();
3653 +
3654 request.pendingChunks++;
3655 const id = request.nextChunkId++;
3638 - emitIOInfoChunk(request, id, name, ioNode.start, ioNode.end, owner, stack);
3656 + emitIOInfoChunk(
3657 + request,
3658 + id,
3659 + name,
3660 + ioNode.start,
3661 + ioNode.end,
3662 + env,
3663 + owner,
3664 + stack,
3665 + );
3666 const ref = serializeByValueID(id);
3667 request.writtenObjects.set(ioNode, ref);
3668 return ref;
@@ -4161,6 +4188,7 @@ function forwardDebugInfo(
4188 const debugAsyncInfo: Omit<ReactAsyncInfo, 'debugTask' | 'debugStack'> =
4189 {
4190 awaited: ioInfo,
4191 + env: debugInfo[i].env,
4192 owner: debugInfo[i].owner,
4193 stack: debugInfo[i].stack,
4194 };
packages/react-server/src/__tests__/ReactFlightAsyncDebugInfo-test.js
+12 -6
@@ -173,6 +173,7 @@ describe('ReactFlightAsyncDebugInfo', () => {
173 {
174 "awaited": {
175 "end": 0,
176 + "env": "Server",
177 "name": "delay",
178 "owner": {
179 "env": "Server",
@@ -219,6 +220,7 @@ describe('ReactFlightAsyncDebugInfo', () => {
220 ],
221 "start": 0,
222 },
223 + "env": "Server",
224 "owner": {
225 "env": "Server",
226 "key": null,
@@ -258,6 +260,7 @@ describe('ReactFlightAsyncDebugInfo', () => {
260 {
261 "awaited": {
262 "end": 0,
263 + "env": "Server",
264 "name": "delay",
265 "owner": {
266 "env": "Server",
@@ -304,6 +307,7 @@ describe('ReactFlightAsyncDebugInfo', () => {
307 ],
308 "start": 0,
309 },
310 + "env": "Server",
311 "owner": {
312 "env": "Server",
313 "key": null,
@@ -394,9 +398,9 @@ describe('ReactFlightAsyncDebugInfo', () => {
398 [
399 "Object.<anonymous>",
400 "/packages/react-server/src/__tests__/ReactFlightAsyncDebugInfo-test.js",
397 - 364,
401 + 368,
402 109,
399 - 351,
403 + 355,
404 67,
405 ],
406 ],
@@ -404,6 +408,7 @@ describe('ReactFlightAsyncDebugInfo', () => {
408 {
409 "awaited": {
410 "end": 0,
411 + "env": "Server",
412 "name": "setTimeout",
413 "owner": {
414 "env": "Server",
@@ -415,9 +420,9 @@ describe('ReactFlightAsyncDebugInfo', () => {
420 [
421 "Object.<anonymous>",
422 "/packages/react-server/src/__tests__/ReactFlightAsyncDebugInfo-test.js",
418 - 364,
423 + 368,
424 109,
420 - 351,
425 + 355,
426 67,
427 ],
428 ],
@@ -426,14 +431,15 @@ describe('ReactFlightAsyncDebugInfo', () => {
431 [
432 "Component",
433 "/packages/react-server/src/__tests__/ReactFlightAsyncDebugInfo-test.js",
429 - 354,
434 + 358,
435 7,
431 - 352,
436 + 356,
437 5,
438 ],
439 ],
440 "start": 0,
441 },
442 + "env": "Server",
443 },
444 {
445 "time": 0,
packages/shared/ReactTypes.js
+2
@@ -234,6 +234,7 @@ export type ReactIOInfo = {
234 +name: string, // the name of the async function being called (e.g. "fetch")
235 +start: number, // the start time
236 +end: number, // the end time (this might be different from the time the await was unblocked)
237 + +env?: string, // the environment where this I/O was spawned.
238 +owner?: null | ReactComponentInfo,
239 +stack?: null | ReactStackTrace,
240 // Stashed Data for the Specific Execution Environment. Not part of the transport protocol
@@ -243,6 +244,7 @@ export type ReactIOInfo = {
244
245 export type ReactAsyncInfo = {
246 +awaited: ReactIOInfo,
247 + +env?: string, // the environment where this was awaited. This might not be the same as where it was spawned.
248 +owner?: null | ReactComponentInfo,
249 +stack?: null | ReactStackTrace,
250 // Stashed Data for the Specific Execution Environment. Not part of the transport protocol