Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[Flight] Encode references to existing objects by property path #28996

Merged
merged 2 commits into from
May 9, 2024

Conversation

sebmarkbage
Copy link
Collaborator

@sebmarkbage sebmarkbage commented May 6, 2024

Instead of forcing an object to be outlined to be able to refer to it later we can refer to it by the property path inside another parent object.

E.g. this encodes such a reference as '$123:props:children:foo:bar'.

That way we don't have to preemptively outline object and we can dedupe after the first time we've found it.

There's no cost on the client if it's not used because we're not storing any additional information preemptively.

This works mainly because we only have simple JSON objects from the root reference. Complex objects like Map, FormData etc. are stored as their entries array in the look up and not the complex object. Other complex objects like TypedArrays or imports don't have deeply nested objects in them that can be referenced.

This solves the problem that we only dedupe after the third instance. This dedupes at the second instance. It also solves the problem where all nested objects inside deduped instances also are outlined.

The property paths can get pretty large. This is why a test on payload size increased. We could potentially outline the reference itself at the first dupe. That way we get a shorter ID to refer to in the third instance.

@sebmarkbage sebmarkbage requested a review from gnoff May 6, 2024 01:27
@facebook-github-bot facebook-github-bot added CLA Signed React Core Team Opened by a member of the React Core Team labels May 6, 2024
@react-sizebot
Copy link

react-sizebot commented May 6, 2024

Comparing: 46abd7b...01fe345

Critical size changes

Includes critical production bundles, as well as any change greater than 2%:

Name +/- Base Current +/- gzip Base gzip Current gzip
oss-stable/react-dom/cjs/react-dom.production.js = 6.66 kB 6.66 kB = 1.82 kB 1.82 kB
oss-stable/react-dom/cjs/react-dom-client.production.js = 492.61 kB 492.61 kB = 87.88 kB 87.88 kB
oss-experimental/react-dom/cjs/react-dom.production.js = 6.67 kB 6.67 kB +0.05% 1.83 kB 1.83 kB
oss-experimental/react-dom/cjs/react-dom-client.production.js = 498.86 kB 498.86 kB = 88.93 kB 88.93 kB
facebook-www/ReactDOM-prod.classic.js = 591.22 kB 591.22 kB = 103.96 kB 103.96 kB
facebook-www/ReactDOM-prod.modern.js = 567.44 kB 567.44 kB = 100.36 kB 100.36 kB
test_utils/ReactAllWarnings.js Deleted 64.26 kB 0.00 kB Deleted 16.02 kB 0.00 kB

Significant size changes

Includes any change greater than 0.2%:

Expand to show
Name +/- Base Current +/- gzip Base gzip Current gzip
oss-stable-semver/react-server-dom-esm/cjs/react-server-dom-esm-client.browser.production.js +0.85% 29.03 kB 29.27 kB +1.57% 6.56 kB 6.67 kB
oss-stable/react-server-dom-esm/cjs/react-server-dom-esm-client.browser.production.js +0.85% 29.03 kB 29.27 kB +1.57% 6.56 kB 6.67 kB
oss-stable-semver/react-client/cjs/react-client-flight.production.js +0.81% 30.52 kB 30.77 kB +1.40% 6.51 kB 6.60 kB
oss-stable/react-client/cjs/react-client-flight.production.js +0.81% 30.52 kB 30.77 kB +1.40% 6.51 kB 6.60 kB
oss-stable-semver/react-server-dom-turbopack/cjs/react-server-dom-turbopack-client.browser.production.js +0.80% 30.57 kB 30.82 kB +1.53% 6.94 kB 7.05 kB
oss-stable/react-server-dom-turbopack/cjs/react-server-dom-turbopack-client.browser.production.js +0.80% 30.57 kB 30.82 kB +1.53% 6.94 kB 7.05 kB
oss-stable-semver/react-server-dom-webpack/cjs/react-server-dom-webpack-client.browser.production.js +0.80% 30.91 kB 31.15 kB +1.48% 7.02 kB 7.12 kB
oss-stable/react-server-dom-webpack/cjs/react-server-dom-webpack-client.browser.production.js +0.80% 30.91 kB 31.15 kB +1.48% 7.02 kB 7.12 kB
oss-stable-semver/react-server-dom-esm/cjs/react-server-dom-esm-client.node.production.js +0.74% 33.22 kB 33.46 kB +1.38% 7.56 kB 7.66 kB
oss-stable/react-server-dom-esm/cjs/react-server-dom-esm-client.node.production.js +0.74% 33.22 kB 33.46 kB +1.38% 7.56 kB 7.66 kB
oss-stable-semver/react-server-dom-turbopack/cjs/react-server-dom-turbopack-client.node.unbundled.production.js +0.71% 34.47 kB 34.71 kB +1.34% 7.83 kB 7.93 kB
oss-stable/react-server-dom-turbopack/cjs/react-server-dom-turbopack-client.node.unbundled.production.js +0.71% 34.47 kB 34.71 kB +1.34% 7.83 kB 7.93 kB
oss-stable-semver/react-server-dom-webpack/cjs/react-server-dom-webpack-client.node.unbundled.production.js +0.71% 34.47 kB 34.71 kB +1.30% 7.84 kB 7.94 kB
oss-stable/react-server-dom-webpack/cjs/react-server-dom-webpack-client.node.unbundled.production.js +0.71% 34.47 kB 34.71 kB +1.30% 7.84 kB 7.94 kB
oss-stable-semver/react-server-dom-webpack/cjs/react-server-dom-webpack-client.node.production.js +0.70% 35.15 kB 35.39 kB +1.27% 8.01 kB 8.11 kB
oss-stable/react-server-dom-webpack/cjs/react-server-dom-webpack-client.node.production.js +0.70% 35.15 kB 35.39 kB +1.27% 8.01 kB 8.11 kB
oss-stable-semver/react-server-dom-turbopack/cjs/react-server-dom-turbopack-client.node.production.js +0.70% 35.16 kB 35.40 kB +1.31% 8.00 kB 8.10 kB
oss-stable/react-server-dom-turbopack/cjs/react-server-dom-turbopack-client.node.production.js +0.70% 35.16 kB 35.40 kB +1.31% 8.00 kB 8.10 kB
oss-stable-semver/react-server-dom-webpack/cjs/react-server-dom-webpack-client.edge.production.js +0.68% 36.38 kB 36.63 kB +1.18% 8.21 kB 8.31 kB
oss-stable/react-server-dom-webpack/cjs/react-server-dom-webpack-client.edge.production.js +0.68% 36.38 kB 36.63 kB +1.18% 8.21 kB 8.31 kB
oss-stable-semver/react-server-dom-turbopack/cjs/react-server-dom-turbopack-client.edge.production.js +0.68% 36.39 kB 36.64 kB +1.22% 8.20 kB 8.30 kB
oss-stable/react-server-dom-turbopack/cjs/react-server-dom-turbopack-client.edge.production.js +0.68% 36.39 kB 36.64 kB +1.22% 8.20 kB 8.30 kB
oss-experimental/react-server-dom-esm/cjs/react-server-dom-esm-client.browser.production.js +0.54% 42.72 kB 42.95 kB +1.21% 8.86 kB 8.97 kB
oss-experimental/react-server/cjs/react-server-flight.production.js +0.53% 56.03 kB 56.33 kB +0.31% 11.14 kB 11.18 kB
oss-experimental/react-server-dom-turbopack/cjs/react-server-dom-turbopack-client.browser.production.js +0.52% 44.26 kB 44.49 kB +1.14% 9.23 kB 9.33 kB
oss-experimental/react-server-dom-webpack/cjs/react-server-dom-webpack-client.browser.production.js +0.52% 44.59 kB 44.83 kB +1.14% 9.31 kB 9.42 kB
oss-experimental/react-client/cjs/react-client-flight.production.js +0.52% 45.39 kB 45.63 kB +1.03% 8.80 kB 8.89 kB
oss-experimental/react-server-dom-esm/cjs/react-server-dom-esm-client.node.production.js +0.49% 47.10 kB 47.33 kB +1.06% 9.89 kB 10.00 kB
oss-experimental/react-server-dom-turbopack/cjs/react-server-dom-turbopack-client.node.unbundled.production.js +0.48% 48.35 kB 48.58 kB +1.05% 10.17 kB 10.27 kB
oss-experimental/react-server-dom-webpack/cjs/react-server-dom-webpack-client.node.unbundled.production.js +0.48% 48.35 kB 48.59 kB +1.01% 10.17 kB 10.27 kB
oss-experimental/react-server-dom-webpack/cjs/react-server-dom-webpack-client.node.production.js +0.47% 49.03 kB 49.26 kB +1.11% 10.34 kB 10.45 kB
oss-experimental/react-server-dom-turbopack/cjs/react-server-dom-turbopack-client.node.production.js +0.47% 49.04 kB 49.27 kB +1.14% 10.32 kB 10.44 kB
oss-experimental/react-server-dom-webpack/cjs/react-server-dom-webpack-client.edge.production.js +0.46% 50.07 kB 50.30 kB +0.94% 10.54 kB 10.64 kB
oss-experimental/react-server-dom-turbopack/cjs/react-server-dom-turbopack-client.edge.production.js +0.46% 50.08 kB 50.31 kB +0.97% 10.52 kB 10.63 kB
oss-stable-semver/react-server-dom-esm/esm/react-server-dom-esm-client.browser.production.js +0.46% 46.38 kB 46.60 kB +0.75% 10.78 kB 10.87 kB
oss-stable/react-server-dom-esm/esm/react-server-dom-esm-client.browser.production.js +0.46% 46.38 kB 46.60 kB +0.75% 10.78 kB 10.87 kB
oss-stable-semver/react-client/cjs/react-client-flight.development.js +0.32% 62.77 kB 62.97 kB +0.48% 15.16 kB 15.23 kB
oss-stable/react-client/cjs/react-client-flight.development.js +0.32% 62.77 kB 62.97 kB +0.48% 15.16 kB 15.23 kB
oss-stable-semver/react-server-dom-esm/esm/react-server-dom-esm-client.browser.development.js +0.32% 62.78 kB 62.98 kB +0.50% 14.87 kB 14.94 kB
oss-stable/react-server-dom-esm/esm/react-server-dom-esm-client.browser.development.js +0.32% 62.78 kB 62.98 kB +0.50% 14.87 kB 14.94 kB
oss-stable-semver/react-server-dom-esm/cjs/react-server-dom-esm-client.browser.development.js +0.32% 63.02 kB 63.22 kB +0.49% 14.93 kB 15.01 kB
oss-stable/react-server-dom-esm/cjs/react-server-dom-esm-client.browser.development.js +0.32% 63.02 kB 63.22 kB +0.49% 14.93 kB 15.01 kB
oss-stable-semver/react-server/cjs/react-server-flight.production.js +0.32% 41.69 kB 41.83 kB +0.26% 9.11 kB 9.13 kB
oss-stable/react-server/cjs/react-server-flight.production.js +0.32% 41.69 kB 41.83 kB +0.26% 9.11 kB 9.13 kB
oss-stable-semver/react-server-dom-turbopack/cjs/react-server-dom-turbopack-client.browser.development.js +0.31% 66.27 kB 66.47 kB +0.47% 15.90 kB 15.97 kB
oss-stable/react-server-dom-turbopack/cjs/react-server-dom-turbopack-client.browser.development.js +0.31% 66.27 kB 66.47 kB +0.47% 15.90 kB 15.97 kB
oss-stable-semver/react-server-dom-webpack/cjs/react-server-dom-webpack-client.browser.development.js +0.30% 66.78 kB 66.98 kB +0.46% 16.07 kB 16.14 kB
oss-stable/react-server-dom-webpack/cjs/react-server-dom-webpack-client.browser.development.js +0.30% 66.78 kB 66.98 kB +0.46% 16.07 kB 16.14 kB
oss-experimental/react-server-dom-esm/cjs/react-server-dom-esm-server.node.production.js +0.29% 83.70 kB 83.94 kB +0.25% 17.26 kB 17.30 kB
oss-experimental/react-server-dom-esm/esm/react-server-dom-esm-client.browser.production.js +0.29% 69.55 kB 69.75 kB +0.50% 15.05 kB 15.13 kB
oss-stable-semver/react-server-dom-esm/cjs/react-server-dom-esm-client.node.development.js +0.29% 70.70 kB 70.90 kB +0.44% 16.97 kB 17.04 kB
oss-stable/react-server-dom-esm/cjs/react-server-dom-esm-client.node.development.js +0.29% 70.70 kB 70.90 kB +0.44% 16.97 kB 17.04 kB
oss-experimental/react-server-dom-turbopack/cjs/react-server-dom-turbopack-server.browser.production.js +0.29% 85.69 kB 85.93 kB +0.23% 17.38 kB 17.42 kB
oss-experimental/react-server-dom-turbopack/cjs/react-server-dom-turbopack-server.edge.production.js +0.28% 86.57 kB 86.82 kB +0.34% 17.63 kB 17.69 kB
oss-experimental/react-server-dom-webpack/cjs/react-server-dom-webpack-server.browser.production.js +0.28% 86.77 kB 87.02 kB +0.25% 17.62 kB 17.66 kB
oss-experimental/react-server-dom-webpack/cjs/react-server-dom-webpack-server.edge.production.js +0.28% 87.30 kB 87.55 kB +0.35% 17.77 kB 17.83 kB
oss-stable-semver/react-server-dom-turbopack/cjs/react-server-dom-turbopack-client.node.unbundled.development.js +0.28% 72.48 kB 72.68 kB +0.42% 17.53 kB 17.60 kB
oss-stable/react-server-dom-turbopack/cjs/react-server-dom-turbopack-client.node.unbundled.development.js +0.28% 72.48 kB 72.68 kB +0.42% 17.53 kB 17.60 kB
oss-stable-semver/react-server-dom-webpack/cjs/react-server-dom-webpack-client.node.unbundled.development.js +0.28% 72.51 kB 72.72 kB +0.41% 17.55 kB 17.63 kB
oss-stable/react-server-dom-webpack/cjs/react-server-dom-webpack-client.node.unbundled.development.js +0.28% 72.51 kB 72.72 kB +0.41% 17.55 kB 17.63 kB
oss-experimental/react-server-dom-turbopack/cjs/react-server-dom-turbopack-server.node.unbundled.production.js +0.28% 88.50 kB 88.75 kB +0.21% 18.01 kB 18.05 kB
oss-experimental/react-server-dom-turbopack/cjs/react-server-dom-turbopack-server.node.production.js +0.27% 89.46 kB 89.71 kB +0.24% 18.22 kB 18.27 kB
oss-stable-semver/react-server-dom-turbopack/cjs/react-server-dom-turbopack-client.node.development.js +0.27% 73.91 kB 74.12 kB +0.39% 17.90 kB 17.97 kB
oss-stable/react-server-dom-turbopack/cjs/react-server-dom-turbopack-client.node.development.js +0.27% 73.91 kB 74.12 kB +0.39% 17.90 kB 17.97 kB
oss-experimental/react-server-dom-webpack/cjs/react-server-dom-webpack-server.node.unbundled.production.js +0.27% 89.60 kB 89.85 kB +0.21% 18.24 kB 18.27 kB
oss-stable-semver/react-server-dom-webpack/cjs/react-server-dom-webpack-client.node.development.js +0.27% 73.94 kB 74.14 kB +0.41% 17.94 kB 18.01 kB
oss-stable/react-server-dom-webpack/cjs/react-server-dom-webpack-client.node.development.js +0.27% 73.94 kB 74.14 kB +0.41% 17.94 kB 18.01 kB
oss-experimental/react-server-dom-webpack/cjs/react-server-dom-webpack-server.node.production.js +0.27% 90.55 kB 90.79 kB +0.25% 18.45 kB 18.49 kB
oss-stable-semver/react-server-dom-turbopack/cjs/react-server-dom-turbopack-client.edge.development.js +0.27% 74.98 kB 75.19 kB +0.39% 18.11 kB 18.18 kB
oss-stable/react-server-dom-turbopack/cjs/react-server-dom-turbopack-client.edge.development.js +0.27% 74.98 kB 75.19 kB +0.39% 18.11 kB 18.18 kB
oss-stable-semver/react-server-dom-webpack/cjs/react-server-dom-webpack-client.edge.development.js +0.27% 75.01 kB 75.21 kB +0.40% 18.15 kB 18.22 kB
oss-stable/react-server-dom-webpack/cjs/react-server-dom-webpack-client.edge.development.js +0.27% 75.01 kB 75.21 kB +0.40% 18.15 kB 18.22 kB
oss-experimental/react-client/cjs/react-client-flight.development.js +0.22% 86.34 kB 86.53 kB +0.34% 19.53 kB 19.60 kB
oss-experimental/react-server-dom-esm/esm/react-server-dom-esm-client.browser.development.js +0.22% 86.35 kB 86.54 kB +0.42% 19.24 kB 19.32 kB
oss-experimental/react-server-dom-esm/cjs/react-server-dom-esm-client.browser.development.js +0.22% 86.59 kB 86.78 kB +0.42% 19.31 kB 19.39 kB
oss-experimental/react-server-dom-turbopack/cjs/react-server-dom-turbopack-client.browser.development.js +0.21% 89.84 kB 90.03 kB +0.39% 20.27 kB 20.35 kB
oss-experimental/react-server-dom-webpack/cjs/react-server-dom-webpack-client.browser.development.js +0.21% 90.35 kB 90.54 kB +0.39% 20.44 kB 20.52 kB
oss-experimental/react-server-dom-esm/cjs/react-server-dom-esm-client.node.development.js +0.20% 94.26 kB 94.46 kB +0.34% 21.34 kB 21.41 kB
oss-stable-semver/react-server/cjs/react-server-flight.development.js = 87.25 kB 87.04 kB = 20.29 kB 20.25 kB
oss-stable/react-server/cjs/react-server-flight.development.js = 87.25 kB 87.04 kB = 20.29 kB 20.25 kB
test_utils/ReactAllWarnings.js Deleted 64.26 kB 0.00 kB Deleted 16.02 kB 0.00 kB

Generated by 🚫 dangerJS against 01fe345

Copy link
Contributor

@unstubbable unstubbable left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is awesome!

@@ -981,8 +992,8 @@ function parseModelString(
}
default: {
// We assume that anything else is a reference ID.
const id = parseInt(value.slice(1), 16);
return getOutlinedModel(response, id, parentObject, key, createModel);
const ref = value.slice(1);
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

nit: To avoid confusion, I would prefer if we called this reference and not ref (here, as well as in the other switch cases), since we also use ref in this file to refer to the actual element ref. getOutlinedModel also calls the param reference.

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It is mainly to avoid all of these calls turning into multi-line.

const serializedContent = await readResult(stream1);
// TODO: Ideally streams should dedupe objects but because we never outline the objects
// they end up not having a row to reference them nor any of its nested objects.
// expect(serializedContent.length).toBeLessThan(400);
Copy link
Collaborator Author

@sebmarkbage sebmarkbage May 6, 2024

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Add a test to show case this. This used to work by the second row getting outlined.

Because every "chunk/entry" inside a stream has the same ID we can't refer to those objects. Only the stream object itself (ReadableStream or AsyncIterable) has a row ID.

This means we can't dedupe the object at the root of a stream row. Since we don't have a reference to that object we also can't dedupe the nested object via the parent neither. Leading to duplicating all these objects.

It seems unfortunate to outline every chunk/entry in a stream just in case it needs deduping.

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Potentially there could be a way to reach into the nth item inside the buffer of the stream to get the Chunk responsible for that and then reach into it.

@sebmarkbage sebmarkbage force-pushed the flightdedupebypath branch 2 times, most recently from b56b1a4 to 01fe345 Compare May 6, 2024 14:39
@@ -543,6 +543,55 @@ describe('ReactFlightDOMEdge', () => {
expect(await iterator.next()).toEqual({value: undefined, done: true});
});

// @gate enableFlightReadableStream
it('dedupes objects inside async iterables', async () => {
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm confused. This is a new test but it asserts that there isn't deduping when the test describes deduping. Can we just gate that it fails until we address the underlying limitation? maybe I am misunderstanding something

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We often just assert on the broken behavior inline with a comment. However, since this entire test is broken I'll name it different.

@sebmarkbage sebmarkbage merged commit 7a78d03 into facebook:main May 9, 2024
35 of 37 checks passed
github-actions bot pushed a commit that referenced this pull request May 9, 2024
Instead of forcing an object to be outlined to be able to refer to it
later we can refer to it by the property path inside another parent
object.

E.g. this encodes such a reference as `'$123:props:children:foo:bar'`.

That way we don't have to preemptively outline object and we can dedupe
after the first time we've found it.

There's no cost on the client if it's not used because we're not storing
any additional information preemptively.

This works mainly because we only have simple JSON objects from the root
reference. Complex objects like Map, FormData etc. are stored as their
entries array in the look up and not the complex object. Other complex
objects like TypedArrays or imports don't have deeply nested objects in
them that can be referenced.

This solves the problem that we only dedupe after the third instance.
This dedupes at the second instance. It also solves the problem where
all nested objects inside deduped instances also are outlined.

The property paths can get pretty large. This is why a test on payload
size increased. We could potentially outline the reference itself at the
first dupe. That way we get a shorter ID to refer to in the third
instance.

DiffTrain build for [7a78d03](7a78d03)
sebmarkbage added a commit that referenced this pull request May 9, 2024
Uses the same technique as in #28996 to encode references to already
emitted objects. This now means that Reply can support cyclic objects
too for parity.
unstubbable added a commit to unstubbable/react that referenced this pull request May 21, 2024
Triggered by vercel/next.js#66033

I was suspecting that the bug was introduced with facebook#28996, but I could not make the test succeed on a commit before that PR, so maybe this assumption is wrong.
gnoff pushed a commit that referenced this pull request May 21, 2024
…ialized' (#29204)

Follow up to #29201. If a chunk
had listeners attached already (e.g. because `.then` was called on the
chunk returned from `createFromReadableStream`),
`wakeChunkIfInitialized` would overwrite any listeners added during
chunk initialization. This caused cyclic [path
references](#28996) within that
chunk to never resolve. Fixed by merging the two arrays of listeners.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
CLA Signed React Core Team Opened by a member of the React Core Team
Projects
None yet
Development

Successfully merging this pull request may close these issues.

5 participants