Skip to content

Commit ab1c43c

Browse files
committed
Reject any new Chunks not yet discovered at the time of reportGlobalError #31840
1 parent 7de040c commit ab1c43c

File tree

2 files changed

+62
-2
lines changed

2 files changed

+62
-2
lines changed

packages/react-client/src/ReactFlightClient.js

Lines changed: 14 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -307,6 +307,8 @@ export type Response = {
307307
_rowTag: number, // 0 indicates that we're currently parsing the row ID
308308
_rowLength: number, // remaining bytes in the row. 0 indicates that we're looking for a newline.
309309
_buffer: Array<Uint8Array>, // chunks received so far as part of this row
310+
_closed: boolean,
311+
_closedReason: mixed,
310312
_tempRefs: void | TemporaryReferenceSet, // the set temporary references can be resolved from
311313
_timeOrigin: number, // Profiling-only
312314
_debugRootOwner?: null | ReactComponentInfo, // DEV-only
@@ -358,7 +360,7 @@ function createBlockedChunk<T>(response: Response): BlockedChunk<T> {
358360

359361
function createErrorChunk<T>(
360362
response: Response,
361-
error: Error | Postpone,
363+
error: mixed,
362364
): ErroredChunk<T> {
363365
// $FlowFixMe[invalid-constructor] Flow doesn't support functions as constructors
364366
return new ReactPromise(ERRORED, null, error, response);
@@ -639,6 +641,8 @@ function initializeModuleChunk<T>(chunk: ResolvedModuleChunk<T>): void {
639641
// Report that any missing chunks in the model is now going to throw this
640642
// error upon read. Also notify any pending promises.
641643
export function reportGlobalError(response: Response, error: Error): void {
644+
response._closed = true;
645+
response._closedReason = error;
642646
response._chunks.forEach(chunk => {
643647
// If this chunk was already resolved or errored, it won't
644648
// trigger an error but if it wasn't then we need to
@@ -913,7 +917,13 @@ function getChunk(response: Response, id: number): SomeChunk<any> {
913917
const chunks = response._chunks;
914918
let chunk = chunks.get(id);
915919
if (!chunk) {
916-
chunk = createPendingChunk(response);
920+
if (response._closed) {
921+
// We have already errored the response and we're not going to get
922+
// anything more streaming in so this will immediately error.
923+
chunk = createErrorChunk(response, response._closedReason);
924+
} else {
925+
chunk = createPendingChunk(response);
926+
}
917927
chunks.set(id, chunk);
918928
}
919929
return chunk;
@@ -1640,6 +1650,8 @@ function ResponseInstance(
16401650
this._rowTag = 0;
16411651
this._rowLength = 0;
16421652
this._buffer = [];
1653+
this._closed = false;
1654+
this._closedReason = null;
16431655
this._tempRefs = temporaryReferences;
16441656
if (enableProfilerTimer && enableComponentPerformanceTrack) {
16451657
this._timeOrigin = 0;

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

Lines changed: 48 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1261,4 +1261,52 @@ describe('ReactFlightDOMEdge', () => {
12611261
div.innerHTML = result;
12621262
expect(div.textContent).toBe('loading...');
12631263
});
1264+
1265+
// @gate enableHalt
1266+
it('should abort parsing an incomplete prerender payload', async () => {
1267+
const infinitePromise = new Promise(() => {});
1268+
const controller = new AbortController();
1269+
const errors = [];
1270+
const {pendingResult} = await serverAct(async () => {
1271+
// destructure trick to avoid the act scope from awaiting the returned value
1272+
return {
1273+
pendingResult: ReactServerDOMStaticServer.unstable_prerender(
1274+
{promise: infinitePromise},
1275+
webpackMap,
1276+
{
1277+
signal: controller.signal,
1278+
onError(err) {
1279+
errors.push(err);
1280+
},
1281+
},
1282+
),
1283+
};
1284+
});
1285+
1286+
controller.abort();
1287+
const {prelude} = await pendingResult;
1288+
1289+
expect(errors).toEqual([]);
1290+
1291+
const response = ReactServerDOMClient.createFromReadableStream(prelude, {
1292+
serverConsumerManifest: {
1293+
moduleMap: {},
1294+
moduleLoading: {},
1295+
},
1296+
});
1297+
1298+
// Wait for the stream to finish and therefore abort before we try to .then the response.
1299+
await 0;
1300+
1301+
const result = await response;
1302+
1303+
let error = null;
1304+
try {
1305+
await result.promise;
1306+
} catch (x) {
1307+
error = x;
1308+
}
1309+
expect(error).not.toBe(null);
1310+
expect(error.message).toBe('Connection closed.');
1311+
});
12641312
});

0 commit comments

Comments
 (0)