Skip to content

Commit 56e8469

Browse files
authored
[Flight] Exclude RSC Stream if the stream resolves in a task (facebook#34838)
1 parent 19b7167 commit 56e8469

File tree

5 files changed

+236
-267
lines changed

5 files changed

+236
-267
lines changed

packages/react-client/src/ReactFlightClient.js

Lines changed: 52 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -367,6 +367,7 @@ type Response = {
367367
_debugRootStack?: null | Error, // DEV-only
368368
_debugRootTask?: null | ConsoleTask, // DEV-only
369369
_debugStartTime: number, // DEV-only
370+
_debugIOStarted: boolean, // DEV-only
370371
_debugFindSourceMapURL?: void | FindSourceMapURLCallback, // DEV-only
371372
_debugChannel?: void | DebugChannel, // DEV-only
372373
_blockedConsole?: null | SomeChunk<ConsoleEntry>, // DEV-only
@@ -500,7 +501,7 @@ function createErrorChunk<T>(
500501
}
501502

502503
function moveDebugInfoFromChunkToInnerValue<T>(
503-
chunk: InitializedChunk<T>,
504+
chunk: InitializedChunk<T> | InitializedStreamChunk<any>,
504505
value: T,
505506
): void {
506507
// Remove the debug info from the initialized chunk, and add it to the inner
@@ -1569,6 +1570,10 @@ function fulfillReference(
15691570
initializedChunk.reason = handler.reason; // Used by streaming chunks
15701571
if (resolveListeners !== null) {
15711572
wakeChunk(resolveListeners, handler.value, initializedChunk);
1573+
} else {
1574+
if (__DEV__) {
1575+
moveDebugInfoFromChunkToInnerValue(initializedChunk, handler.value);
1576+
}
15721577
}
15731578
}
15741579
}
@@ -1818,6 +1823,10 @@ function loadServerReference<A: Iterable<any>, T>(
18181823
initializedChunk.value = handler.value;
18191824
if (resolveListeners !== null) {
18201825
wakeChunk(resolveListeners, handler.value, initializedChunk);
1826+
} else {
1827+
if (__DEV__) {
1828+
moveDebugInfoFromChunkToInnerValue(initializedChunk, handler.value);
1829+
}
18211830
}
18221831
}
18231832
}
@@ -2536,6 +2545,10 @@ function missingCall() {
25362545
);
25372546
}
25382547

2548+
function markIOStarted(this: Response) {
2549+
this._debugIOStarted = true;
2550+
}
2551+
25392552
function ResponseInstance(
25402553
this: $FlowFixMe,
25412554
bundlerConfig: ServerConsumerModuleMap,
@@ -2609,6 +2622,10 @@ function ResponseInstance(
26092622
// where as if you use createFromReadableStream from the body of the fetch
26102623
// then the start time is when the headers resolved.
26112624
this._debugStartTime = performance.now();
2625+
this._debugIOStarted = false;
2626+
// We consider everything before the first setTimeout task to be cached data
2627+
// and is not considered I/O required to load the stream.
2628+
setTimeout(markIOStarted.bind(this), 0);
26122629
}
26132630
this._debugFindSourceMapURL = findSourceMapURL;
26142631
this._debugChannel = debugChannel;
@@ -2762,7 +2779,7 @@ function incrementChunkDebugInfo(
27622779
}
27632780
}
27642781

2765-
function addDebugInfo(chunk: SomeChunk<any>, debugInfo: ReactDebugInfo): void {
2782+
function addAsyncInfo(chunk: SomeChunk<any>, asyncInfo: ReactAsyncInfo): void {
27662783
const value = resolveLazy(chunk.value);
27672784
if (
27682785
typeof value === 'object' &&
@@ -2774,34 +2791,39 @@ function addDebugInfo(chunk: SomeChunk<any>, debugInfo: ReactDebugInfo): void {
27742791
) {
27752792
if (isArray(value._debugInfo)) {
27762793
// $FlowFixMe[method-unbinding]
2777-
value._debugInfo.push.apply(value._debugInfo, debugInfo);
2794+
value._debugInfo.push(asyncInfo);
27782795
} else {
27792796
Object.defineProperty((value: any), '_debugInfo', {
27802797
configurable: false,
27812798
enumerable: false,
27822799
writable: true,
2783-
value: debugInfo,
2800+
value: [asyncInfo],
27842801
});
27852802
}
27862803
} else {
27872804
// $FlowFixMe[method-unbinding]
2788-
chunk._debugInfo.push.apply(chunk._debugInfo, debugInfo);
2805+
chunk._debugInfo.push(asyncInfo);
27892806
}
27902807
}
27912808

27922809
function resolveChunkDebugInfo(
2810+
response: Response,
27932811
streamState: StreamState,
27942812
chunk: SomeChunk<any>,
27952813
): void {
27962814
if (__DEV__ && enableAsyncDebugInfo) {
2797-
// Add the currently resolving chunk's debug info representing the stream
2798-
// to the Promise that was waiting on the stream, or its underlying value.
2799-
const debugInfo: ReactDebugInfo = [{awaited: streamState._debugInfo}];
2800-
if (chunk.status === PENDING || chunk.status === BLOCKED) {
2801-
const boundAddDebugInfo = addDebugInfo.bind(null, chunk, debugInfo);
2802-
chunk.then(boundAddDebugInfo, boundAddDebugInfo);
2803-
} else {
2804-
addDebugInfo(chunk, debugInfo);
2815+
// Only include stream information after a macrotask. Any chunk processed
2816+
// before that is considered cached data.
2817+
if (response._debugIOStarted) {
2818+
// Add the currently resolving chunk's debug info representing the stream
2819+
// to the Promise that was waiting on the stream, or its underlying value.
2820+
const asyncInfo: ReactAsyncInfo = {awaited: streamState._debugInfo};
2821+
if (chunk.status === PENDING || chunk.status === BLOCKED) {
2822+
const boundAddAsyncInfo = addAsyncInfo.bind(null, chunk, asyncInfo);
2823+
chunk.then(boundAddAsyncInfo, boundAddAsyncInfo);
2824+
} else {
2825+
addAsyncInfo(chunk, asyncInfo);
2826+
}
28052827
}
28062828
}
28072829
}
@@ -2837,12 +2859,12 @@ function resolveModel(
28372859
model,
28382860
);
28392861
if (__DEV__) {
2840-
resolveChunkDebugInfo(streamState, newChunk);
2862+
resolveChunkDebugInfo(response, streamState, newChunk);
28412863
}
28422864
chunks.set(id, newChunk);
28432865
} else {
28442866
if (__DEV__) {
2845-
resolveChunkDebugInfo(streamState, chunk);
2867+
resolveChunkDebugInfo(response, streamState, chunk);
28462868
}
28472869
resolveModelChunk(response, chunk, model);
28482870
}
@@ -2869,7 +2891,7 @@ function resolveText(
28692891
}
28702892
const newChunk = createInitializedTextChunk(response, text);
28712893
if (__DEV__) {
2872-
resolveChunkDebugInfo(streamState, newChunk);
2894+
resolveChunkDebugInfo(response, streamState, newChunk);
28732895
}
28742896
chunks.set(id, newChunk);
28752897
}
@@ -2895,7 +2917,7 @@ function resolveBuffer(
28952917
}
28962918
const newChunk = createInitializedBufferChunk(response, buffer);
28972919
if (__DEV__) {
2898-
resolveChunkDebugInfo(streamState, newChunk);
2920+
resolveChunkDebugInfo(response, streamState, newChunk);
28992921
}
29002922
chunks.set(id, newChunk);
29012923
}
@@ -2942,7 +2964,7 @@ function resolveModule(
29422964
blockedChunk.status = BLOCKED;
29432965
}
29442966
if (__DEV__) {
2945-
resolveChunkDebugInfo(streamState, blockedChunk);
2967+
resolveChunkDebugInfo(response, streamState, blockedChunk);
29462968
}
29472969
promise.then(
29482970
() => resolveModuleChunk(response, blockedChunk, clientReference),
@@ -2952,12 +2974,12 @@ function resolveModule(
29522974
if (!chunk) {
29532975
const newChunk = createResolvedModuleChunk(response, clientReference);
29542976
if (__DEV__) {
2955-
resolveChunkDebugInfo(streamState, newChunk);
2977+
resolveChunkDebugInfo(response, streamState, newChunk);
29562978
}
29572979
chunks.set(id, newChunk);
29582980
} else {
29592981
if (__DEV__) {
2960-
resolveChunkDebugInfo(streamState, chunk);
2982+
resolveChunkDebugInfo(response, streamState, chunk);
29612983
}
29622984
// This can't actually happen because we don't have any forward
29632985
// references to modules.
@@ -2978,13 +3000,13 @@ function resolveStream<T: ReadableStream | $AsyncIterable<any, any, void>>(
29783000
if (!chunk) {
29793001
const newChunk = createInitializedStreamChunk(response, stream, controller);
29803002
if (__DEV__) {
2981-
resolveChunkDebugInfo(streamState, newChunk);
3003+
resolveChunkDebugInfo(response, streamState, newChunk);
29823004
}
29833005
chunks.set(id, newChunk);
29843006
return;
29853007
}
29863008
if (__DEV__) {
2987-
resolveChunkDebugInfo(streamState, chunk);
3009+
resolveChunkDebugInfo(response, streamState, chunk);
29883010
}
29893011
if (chunk.status !== PENDING) {
29903012
// We already resolved. We didn't expect to see this.
@@ -3034,6 +3056,10 @@ function resolveStream<T: ReadableStream | $AsyncIterable<any, any, void>>(
30343056
resolvedChunk.reason = controller;
30353057
if (resolveListeners !== null) {
30363058
wakeChunk(resolveListeners, chunk.value, (chunk: any));
3059+
} else {
3060+
if (__DEV__) {
3061+
moveDebugInfoFromChunkToInnerValue(resolvedChunk, stream);
3062+
}
30373063
}
30383064
}
30393065

@@ -3433,12 +3459,12 @@ function resolvePostponeDev(
34333459
postponeInstance,
34343460
);
34353461
if (__DEV__) {
3436-
resolveChunkDebugInfo(streamState, newChunk);
3462+
resolveChunkDebugInfo(response, streamState, newChunk);
34373463
}
34383464
chunks.set(id, newChunk);
34393465
} else {
34403466
if (__DEV__) {
3441-
resolveChunkDebugInfo(streamState, chunk);
3467+
resolveChunkDebugInfo(response, streamState, chunk);
34423468
}
34433469
triggerErrorOnChunk(response, chunk, postponeInstance);
34443470
}
@@ -3467,12 +3493,12 @@ function resolveErrorModel(
34673493
errorWithDigest,
34683494
);
34693495
if (__DEV__) {
3470-
resolveChunkDebugInfo(streamState, newChunk);
3496+
resolveChunkDebugInfo(response, streamState, newChunk);
34713497
}
34723498
chunks.set(id, newChunk);
34733499
} else {
34743500
if (__DEV__) {
3475-
resolveChunkDebugInfo(streamState, chunk);
3501+
resolveChunkDebugInfo(response, streamState, chunk);
34763502
}
34773503
triggerErrorOnChunk(response, chunk, errorWithDigest);
34783504
}

packages/react-server-dom-webpack/src/__tests__/ReactFlightDOMBrowser-test.js

Lines changed: 0 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -3035,18 +3035,6 @@ describe('ReactFlightDOMBrowser', () => {
30353035
{
30363036
"time": 0,
30373037
},
3038-
{
3039-
"awaited": {
3040-
"byteSize": 0,
3041-
"end": 0,
3042-
"name": "RSC stream",
3043-
"owner": null,
3044-
"start": 0,
3045-
"value": {
3046-
"value": "stream",
3047-
},
3048-
},
3049-
},
30503038
]
30513039
`);
30523040
}

packages/react-server-dom-webpack/src/__tests__/ReactFlightDOMEdge-test.js

Lines changed: 0 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1248,11 +1248,6 @@ describe('ReactFlightDOMEdge', () => {
12481248
owner: greetInfo,
12491249
}),
12501250
{time: 14},
1251-
expect.objectContaining({
1252-
awaited: expect.objectContaining({
1253-
name: 'RSC stream',
1254-
}),
1255-
}),
12561251
]);
12571252
}
12581253
// The owner that created the span was the outer server component.

packages/react-server/src/ReactFlightServer.js

Lines changed: 8 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -2325,9 +2325,15 @@ function visitAsyncNode(
23252325
return null;
23262326
}
23272327
visited.add(node);
2328+
if (node.end >= 0 && node.end <= request.timeOrigin) {
2329+
// This was already resolved when we started this render. It must have been either something
2330+
// that's part of a start up sequence or externally cached data. We exclude that information.
2331+
// The technique for debugging the effects of uncached data on the render is to simply uncache it.
2332+
return null;
2333+
}
23282334
let previousIONode = null;
23292335
// First visit anything that blocked this sequence to start in the first place.
2330-
if (node.previous !== null && node.end > request.timeOrigin) {
2336+
if (node.previous !== null) {
23312337
previousIONode = visitAsyncNode(
23322338
request,
23332339
task,
@@ -2349,12 +2355,6 @@ function visitAsyncNode(
23492355
return previousIONode;
23502356
}
23512357
case PROMISE_NODE: {
2352-
if (node.end <= request.timeOrigin) {
2353-
// This was already resolved when we started this render. It must have been either something
2354-
// that's part of a start up sequence or externally cached data. We exclude that information.
2355-
// The technique for debugging the effects of uncached data on the render is to simply uncache it.
2356-
return previousIONode;
2357-
}
23582358
const awaited = node.awaited;
23592359
let match: void | null | PromiseNode | IONode = previousIONode;
23602360
const promise = node.promise.deref();
@@ -2437,11 +2437,7 @@ function visitAsyncNode(
24372437
} else if (ioNode !== null) {
24382438
const startTime: number = node.start;
24392439
const endTime: number = node.end;
2440-
if (endTime <= request.timeOrigin) {
2441-
// This was already resolved when we started this render. It must have been either something
2442-
// that's part of a start up sequence or externally cached data. We exclude that information.
2443-
return null;
2444-
} else if (startTime < cutOff) {
2440+
if (startTime < cutOff) {
24452441
// We started awaiting this node before we started rendering this sequence.
24462442
// This means that this particular await was never part of the current sequence.
24472443
// If we have another await higher up in the chain it might have a more actionable stack

0 commit comments

Comments
 (0)