Skip to content

Commit b9cfa0d

Browse files
authored
[Flight] Prevent serialized size leaking across requests (facebook#33121)
1 parent c129c24 commit b9cfa0d

File tree

2 files changed

+49
-12
lines changed

2 files changed

+49
-12
lines changed

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

Lines changed: 38 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -692,6 +692,44 @@ describe('ReactFlightDOMEdge', () => {
692692
expect(html).toBe(html2);
693693
});
694694

695+
it('regression: should not leak serialized size', async () => {
696+
const MAX_ROW_SIZE = 3200;
697+
// This test case is a bit convoluted and may no longer trigger the original bug.
698+
// Originally, the size of `promisedText` was not cleaned up so the sync portion
699+
// ended up being deferred immediately when we called `renderToReadableStream` again
700+
// i.e. `result2.syncText` became a Lazy element on the second request.
701+
const longText = 'd'.repeat(MAX_ROW_SIZE);
702+
const promisedText = Promise.resolve(longText);
703+
const model = {syncText: <p>{longText}</p>, promisedText};
704+
705+
const stream = await serverAct(() =>
706+
ReactServerDOMServer.renderToReadableStream(model),
707+
);
708+
709+
const result = await ReactServerDOMClient.createFromReadableStream(stream, {
710+
serverConsumerManifest: {
711+
moduleMap: null,
712+
moduleLoading: null,
713+
},
714+
});
715+
716+
const stream2 = await serverAct(() =>
717+
ReactServerDOMServer.renderToReadableStream(model),
718+
);
719+
720+
const result2 = await ReactServerDOMClient.createFromReadableStream(
721+
stream2,
722+
{
723+
serverConsumerManifest: {
724+
moduleMap: null,
725+
moduleLoading: null,
726+
},
727+
},
728+
);
729+
730+
expect(result2.syncText).toEqual(result.syncText);
731+
});
732+
695733
it('should be able to serialize any kind of typed array', async () => {
696734
const buffer = new Uint8Array([
697735
123, 4, 10, 5, 100, 255, 244, 45, 56, 67, 43, 124, 67, 89, 100, 20,

packages/react-server/src/ReactFlightServer.js

Lines changed: 11 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -3927,18 +3927,9 @@ function emitChunk(
39273927
return;
39283928
}
39293929
// For anything else we need to try to serialize it using JSON.
3930-
// We stash the outer parent size so we can restore it when we exit.
3931-
const parentSerializedSize = serializedSize;
3932-
// We don't reset the serialized size counter from reentry because that indicates that we
3933-
// are outlining a model and we actually want to include that size into the parent since
3934-
// it will still block the parent row. It only restores to zero at the top of the stack.
3935-
try {
3936-
// $FlowFixMe[incompatible-type] stringify can return null for undefined but we never do
3937-
const json: string = stringify(value, task.toJSON);
3938-
emitModelChunk(request, task.id, json);
3939-
} finally {
3940-
serializedSize = parentSerializedSize;
3941-
}
3930+
// $FlowFixMe[incompatible-type] stringify can return null for undefined but we never do
3931+
const json: string = stringify(value, task.toJSON);
3932+
emitModelChunk(request, task.id, json);
39423933
}
39433934

39443935
function erroredTask(request: Request, task: Task, error: mixed): void {
@@ -3976,6 +3967,11 @@ function retryTask(request: Request, task: Task): void {
39763967
const prevDebugID = debugID;
39773968
task.status = RENDERING;
39783969

3970+
// We stash the outer parent size so we can restore it when we exit.
3971+
const parentSerializedSize = serializedSize;
3972+
// We don't reset the serialized size counter from reentry because that indicates that we
3973+
// are outlining a model and we actually want to include that size into the parent since
3974+
// it will still block the parent row. It only restores to zero at the top of the stack.
39793975
try {
39803976
// Track the root so we know that we have to emit this object even though it
39813977
// already has an ID. This is needed because we might see this object twice
@@ -4087,6 +4083,7 @@ function retryTask(request: Request, task: Task): void {
40874083
if (__DEV__) {
40884084
debugID = prevDebugID;
40894085
}
4086+
serializedSize = parentSerializedSize;
40904087
}
40914088
}
40924089

@@ -4099,9 +4096,11 @@ function tryStreamTask(request: Request, task: Task): void {
40994096
// so that we instead outline the row to get a new debugID if needed.
41004097
debugID = null;
41014098
}
4099+
const parentSerializedSize = serializedSize;
41024100
try {
41034101
emitChunk(request, task, task.model);
41044102
} finally {
4103+
serializedSize = parentSerializedSize;
41054104
if (__DEV__) {
41064105
debugID = prevDebugID;
41074106
}

0 commit comments

Comments
 (0)