@samitouri / QOS-React-2 / commits / 223f81d877

[Flight] Flush performance track once we have no more pending chunks (#33719)

Stacked on #33718. Alternative to #33716. The issue with flushing the Server Components track in its current form is that we need to decide how long to wait before flushing whatever we have. That's because the root's end time will be determined by the end time of that last child. However, if a child isn't actually used then we don't necessarily need to include it in the Server Components track since it wasn't blocking the initial render. This waits for 100ms after the last pending chunk is resolved and if nothing is invoking any more lazy initializers after that then we log the Server Components track with the information we have at that point. We also don't eagerly initialize any chunks that wasn't already initialized so if nothing was rendered, then nothing will be logged. This is somewhat an artifact of the current visualization. If we did another transposed form we wouldn't necessarily need to wait until the end and can log things as they're discovered.

Sebastian Markbåge committed Jul 7, 2025 at 11:42 UTC 223f81d87728cdc843baa4fc5704c2f3b66fbd45
1 file changed +31 -20
packages/react-client/src/ReactFlightClient.js
+31 -20
@@ -351,6 +351,7 @@ type Response = {
351 _closedReason: mixed,
352 _tempRefs: void | TemporaryReferenceSet, // the set temporary references can be resolved from
353 _timeOrigin: number, // Profiling-only
354 + _pendingInitialRender: null | TimeoutID, // Profiling-only,
355 _pendingChunks: number, // DEV-only
356 _weakResponse: WeakResponse, // DEV-only
357 _debugRootOwner?: null | ReactComponentInfo, // DEV-only
@@ -444,8 +445,13 @@ export function getRoot<T>(weakResponse: WeakResponse): Thenable<T> {
445 function createPendingChunk<T>(response: Response): PendingChunk<T> {
446 if (__DEV__) {
447 // Retain a strong reference to the Response while we wait for the result.
447 - response._pendingChunks++;
448 - response._weakResponse.response = response;
448 + if (response._pendingChunks++ === 0) {
449 + response._weakResponse.response = response;
450 + if (response._pendingInitialRender !== null) {
451 + clearTimeout(response._pendingInitialRender);
452 + response._pendingInitialRender = null;
453 + }
454 + }
455 }
456 // $FlowFixMe[invalid-constructor] Flow doesn't support functions as constructors
457 return new ReactPromise(PENDING, null, null);
@@ -457,6 +463,14 @@ function releasePendingChunk(response: Response, chunk: SomeChunk<any>): void {
463 // We're no longer waiting for any more chunks. We can release the strong reference
464 // to the response. We'll regain it if we ask for any more data later on.
465 response._weakResponse.response = null;
466 + // Wait a short period to see if any more chunks get asked for. E.g. by a React render.
467 + // These chunks might discover more pending chunks.
468 + // If we don't ask for more then we assume that those chunks weren't blocking initial
469 + // render and are excluded from the performance track.
470 + response._pendingInitialRender = setTimeout(
471 + flushInitialRenderPerformance.bind(null, response),
472 + 100,
473 + );
474 }
475 }
476 }
@@ -868,18 +882,6 @@ export function reportGlobalError(
882 response._debugChannel = undefined;
883 }
884 }
871 - if (enableProfilerTimer && enableComponentPerformanceTrack) {
872 - if (response._replayConsole) {
873 - markAllTracksInOrder();
874 - flushComponentPerformance(
875 - response,
876 - getChunk(response, 0),
877 - 0,
878 - -Infinity,
879 - -Infinity,
880 - );
881 - }
882 - }
885 }
886
887 function nullRefGetter() {
@@ -2105,6 +2107,7 @@ function ResponseInstance(
2107 this._tempRefs = temporaryReferences;
2108 if (enableProfilerTimer && enableComponentPerformanceTrack) {
2109 this._timeOrigin = 0;
2110 + this._pendingInitialRender = null;
2111 }
2112 if (__DEV__) {
2113 this._pendingChunks = 0;
@@ -3491,12 +3494,6 @@ function flushComponentPerformance(
3494 return previousResult;
3495 }
3496 const children = root._children;
3494 - if (root.status === RESOLVED_MODEL) {
3495 - // If the model is not initialized by now, do that now so we can find its
3496 - // children. This part is a little sketchy since it significantly changes
3497 - // the performance characteristics of the app by profiling.
3498 - initializeModelChunk(root);
3499 - }
3497
3498 // First find the start time of the first component to know if it was running
3499 // in parallel with the previous.
@@ -3703,6 +3700,20 @@ function flushComponentPerformance(
3700 return result;
3701 }
3702
3703 +function flushInitialRenderPerformance(response: Response): void {
3704 + if (
3705 + enableProfilerTimer &&
3706 + enableComponentPerformanceTrack &&
3707 + response._replayConsole
3708 + ) {
3709 + const rootChunk = getChunk(response, 0);
3710 + if (isArray(rootChunk._children)) {
3711 + markAllTracksInOrder();
3712 + flushComponentPerformance(response, rootChunk, 0, -Infinity, -Infinity);
3713 + }
3714 + }
3715 +}
3716 +
3717 function processFullBinaryRow(
3718 response: Response,
3719 id: number,