@samitouri / QOS-React-2 / commits / 1540081725

[Flight] Encode Async I/O Tasks using the Enclosing Line/Column (#33403)

Stacked on #33402. There's a bug in Chrome Performance tracking which uses the enclosing line/column instead of the callsite in stacks. For our fake eval:ed functions that represents functions on the server, we can position the enclosing function body at the position of the callsite to simulate getting the right line. Unfortunately, that doesn't give us exactly the right callsite when it's used for other purposes that uses the callsite like console logs and error reporting and stacks inside breakpoints. So I don't think we want to always do this. For ReactAsyncInfo/ReactIOInfo, the only thing we're going to use the fake task for is the Performance tracking, so it doesn't have any downsides until Chrome fixes the bug and we'd have to revert it. Therefore this PR uses that techniques only for those entries. We could do this for Server Components too but we're going to use those for other things too like console logs. I don't think it's worth duplicating the Task objects. That would also make it inconsistent with Client Components. For Client Components, we could in theory also generate fake evals but that would be way slower since there's so many of them and currently we rely on the native implementation for those. So doesn't seem worth fixing. But since we can at least fix it for RSC I/O/awaits we can do this hack.

Sebastian Markbåge committed Jun 3, 2025 at 17:30 UTC 154008172573d64519ebbc23da611a27073b0a8c
1 file changed +38 -5
packages/react-client/src/ReactFlightClient.js
+38 -5
@@ -813,7 +813,13 @@ function createElement(
813 console,
814 getTaskName(type),
815 );
816 - const callStack = buildFakeCallStack(response, stack, env, createTaskFn);
816 + const callStack = buildFakeCallStack(
817 + response,
818 + stack,
819 + env,
820 + false,
821 + createTaskFn,
822 + );
823 // This owner should ideally have already been initialized to avoid getting
824 // user stack frames on the stack.
825 const ownerTask =
@@ -2134,6 +2140,7 @@ function resolveErrorDev(
2140 response,
2141 stack,
2142 env,
2143 + false,
2144 // $FlowFixMe[incompatible-use]
2145 Error.bind(
2146 null,
@@ -2196,6 +2203,7 @@ function resolvePostponeDev(
2203 response,
2204 stack,
2205 env,
2206 + false,
2207 // $FlowFixMe[incompatible-use]
2208 Error.bind(null, reason || ''),
2209 );
@@ -2404,12 +2412,17 @@ function buildFakeCallStack<T>(
2412 response: Response,
2413 stack: ReactStackTrace,
2414 environmentName: string,
2415 + useEnclosingLine: boolean,
2416 innerCall: () => T,
2417 ): () => T {
2418 let callStack = innerCall;
2419 for (let i = 0; i < stack.length; i++) {
2420 const frame = stack[i];
2412 - const frameKey = frame.join('-') + '-' + environmentName;
2421 + const frameKey =
2422 + frame.join('-') +
2423 + '-' +
2424 + environmentName +
2425 + (useEnclosingLine ? '-e' : '-n');
2426 let fn = fakeFunctionCache.get(frameKey);
2427 if (fn === undefined) {
2428 const [name, filename, line, col, enclosingLine, enclosingCol] = frame;
@@ -2423,8 +2436,8 @@ function buildFakeCallStack<T>(
2436 sourceMap,
2437 line,
2438 col,
2426 - enclosingLine,
2427 - enclosingCol,
2439 + useEnclosingLine ? line : enclosingLine,
2440 + useEnclosingLine ? col : enclosingCol,
2441 environmentName,
2442 );
2443 // TODO: This cache should technically live on the response since the _debugFindSourceMapURL
@@ -2470,6 +2483,15 @@ function initializeFakeTask(
2483 // If it's null, we can't initialize a task.
2484 return null;
2485 }
2486 +
2487 + // Workaround for a bug where Chrome Performance tracking uses the enclosing line/column
2488 + // instead of the callsite. For ReactAsyncInfo/ReactIOInfo, the only thing we're going
2489 + // to use the fake task for is the Performance tracking so we encode the enclosing line/
2490 + // column at the callsite to get a better line number. We could do this for Components too
2491 + // but we're going to use those for other things too like console logs and it's not worth
2492 + // duplicating. If this bug is every fixed in Chrome, this should be set to false.
2493 + const useEnclosingLine = debugInfo.key === undefined;
2494 +
2495 const stack = debugInfo.stack;
2496 const env: string =
2497 debugInfo.env == null ? response._rootEnvironmentName : debugInfo.env;
@@ -2486,6 +2508,7 @@ function initializeFakeTask(
2508 stack,
2509 '"use ' + childEnvironmentName.toLowerCase() + '"',
2510 env,
2511 + useEnclosingLine,
2512 );
2513 } else {
2514 const cachedEntry = debugInfo.debugTask;
@@ -2510,6 +2533,7 @@ function initializeFakeTask(
2533 stack,
2534 taskName,
2535 env,
2536 + useEnclosingLine,
2537 ));
2538 }
2539 }
@@ -2520,9 +2544,16 @@ function buildFakeTask(
2544 stack: ReactStackTrace,
2545 taskName: string,
2546 env: string,
2547 + useEnclosingLine: boolean,
2548 ): ConsoleTask {
2549 const createTaskFn = (console: any).createTask.bind(console, taskName);
2525 - const callStack = buildFakeCallStack(response, stack, env, createTaskFn);
2550 + const callStack = buildFakeCallStack(
2551 + response,
2552 + stack,
2553 + env,
2554 + useEnclosingLine,
2555 + createTaskFn,
2556 + );
2557 if (ownerTask === null) {
2558 const rootTask = getRootTask(response, env);
2559 if (rootTask != null) {
@@ -2545,6 +2576,7 @@ const createFakeJSXCallStack = {
2576 response,
2577 stack,
2578 environmentName,
2579 + false,
2580 fakeJSXCallSite,
2581 );
2582 return callStackForError();
@@ -2681,6 +2713,7 @@ const replayConsoleWithCallStack = {
2713 response,
2714 stackTrace,
2715 env,
2716 + false,
2717 bindToConsole(methodName, args, env),
2718 );
2719 if (owner != null) {