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 Reply] Don't create temporary references for objects whose parts were consumed during serialization #32702

Open
wants to merge 1 commit into
base: main
Choose a base branch
from

Conversation

lubieowoce
Copy link
Contributor

@lubieowoce lubieowoce commented Mar 21, 2025

This PR fixes a bug with passing "consumable" values from the client to the server and back. Consumable values include Iterators, AsyncIterators, and ReadableStreams -- "one-shot" values that we can only iterate over once.

The problem occurs if we attempt to roundtrip an object like { wrapper: iterator }.
When decoding the reply, we'd currently create a temporary reference for the object, and since in the server is passing it back unchanged, we'd just return a temporary reference instead of serializing again.
However, the iterator was already consumed when the client was encoding the reply, so the final response would contain a non-usable, exhausted iterator. Same goes for AsyncIterators and ReadableStreams (which would throw when read instead of silently being done).

This PR fixes that by making reviveModel check if an object contains parts that were already consumed during serialization. If it does, we skip the creation of a temporary reference for the object itself. This propagates outwards: { outer: { inner: iterator }} should also not be returned as a temporary reference for the same reasons.
Then, when serializing the response, we won't have a temporary reference for the object(s), so we'll re-serialize them and the deserialized iterator, and thus give the client a usable value.

Note that roundtripping a plain iterator (without any wrapper objects around it) is not affected by this bug, because we don't currently create temporary references for values with special decoding logic (i.e. ones returned by parseModelString), only for plain objects / arrays, so the value would already always be re-serialized. I added tests for this as well just to make sure.

@react-sizebot
Copy link

react-sizebot commented Mar 21, 2025

Comparing: e1e7407...ab472d7

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.68 kB 6.68 kB +0.11% 1.83 kB 1.83 kB
oss-stable/react-dom/cjs/react-dom-client.production.js = 517.29 kB 517.29 kB = 92.26 kB 92.26 kB
oss-experimental/react-dom/cjs/react-dom.production.js = 6.69 kB 6.69 kB +0.05% 1.83 kB 1.83 kB
oss-experimental/react-dom/cjs/react-dom-client.production.js = 617.68 kB 617.68 kB = 109.55 kB 109.55 kB
facebook-www/ReactDOM-prod.classic.js = 653.74 kB 653.74 kB = 115.19 kB 115.20 kB
facebook-www/ReactDOM-prod.modern.js = 644.02 kB 644.02 kB = 113.61 kB 113.61 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-parcel/cjs/react-server-dom-parcel-server.browser.production.js +1.93% 86.29 kB 87.96 kB +1.48% 18.00 kB 18.27 kB
oss-stable/react-server-dom-parcel/cjs/react-server-dom-parcel-server.browser.production.js +1.93% 86.29 kB 87.96 kB +1.48% 18.00 kB 18.27 kB
oss-stable-semver/react-server-dom-parcel/cjs/react-server-dom-parcel-server.edge.production.js +1.90% 87.73 kB 89.40 kB +1.43% 18.33 kB 18.59 kB
oss-stable/react-server-dom-parcel/cjs/react-server-dom-parcel-server.edge.production.js +1.90% 87.73 kB 89.40 kB +1.43% 18.33 kB 18.59 kB
oss-stable-semver/react-server-dom-esm/cjs/react-server-dom-esm-server.node.production.js +1.86% 89.76 kB 91.43 kB +1.44% 18.74 kB 19.01 kB
oss-stable/react-server-dom-esm/cjs/react-server-dom-esm-server.node.production.js +1.86% 89.76 kB 91.43 kB +1.44% 18.74 kB 19.01 kB
oss-stable-semver/react-server-dom-parcel/cjs/react-server-dom-parcel-server.node.production.js +1.86% 89.79 kB 91.46 kB +1.45% 18.75 kB 19.02 kB
oss-stable/react-server-dom-parcel/cjs/react-server-dom-parcel-server.node.production.js +1.86% 89.79 kB 91.46 kB +1.45% 18.75 kB 19.02 kB
oss-experimental/react-server-dom-parcel/cjs/react-server-dom-parcel-server.browser.production.js +1.84% 90.90 kB 92.57 kB +1.43% 18.76 kB 19.02 kB
oss-experimental/react-server-dom-parcel/cjs/react-server-dom-parcel-server.edge.production.js +1.81% 92.43 kB 94.10 kB +1.37% 19.09 kB 19.35 kB
oss-stable-semver/react-server-dom-turbopack/cjs/react-server-dom-turbopack-server.browser.production.js +1.79% 93.21 kB 94.88 kB +1.39% 19.10 kB 19.37 kB
oss-stable/react-server-dom-turbopack/cjs/react-server-dom-turbopack-server.browser.production.js +1.79% 93.21 kB 94.88 kB +1.39% 19.10 kB 19.37 kB
oss-stable-semver/react-server-dom-webpack/cjs/react-server-dom-webpack-server.browser.production.js +1.78% 93.62 kB 95.29 kB +1.37% 19.21 kB 19.48 kB
oss-stable/react-server-dom-webpack/cjs/react-server-dom-webpack-server.browser.production.js +1.78% 93.62 kB 95.29 kB +1.37% 19.21 kB 19.48 kB
oss-experimental/react-server-dom-esm/cjs/react-server-dom-esm-server.node.production.js +1.77% 94.32 kB 95.99 kB +1.63% 19.50 kB 19.82 kB
oss-experimental/react-server-dom-parcel/cjs/react-server-dom-parcel-server.node.production.js +1.77% 94.35 kB 96.02 kB +1.64% 19.52 kB 19.84 kB
oss-stable-semver/react-server-dom-turbopack/cjs/react-server-dom-turbopack-server.edge.production.js +1.76% 94.67 kB 96.34 kB +1.34% 19.44 kB 19.70 kB
oss-stable/react-server-dom-turbopack/cjs/react-server-dom-turbopack-server.edge.production.js +1.76% 94.67 kB 96.34 kB +1.34% 19.44 kB 19.70 kB
oss-stable-semver/react-server-dom-webpack/cjs/react-server-dom-webpack-server.edge.production.js +1.76% 94.73 kB 96.40 kB +1.35% 19.46 kB 19.72 kB
oss-stable/react-server-dom-webpack/cjs/react-server-dom-webpack-server.edge.production.js +1.76% 94.73 kB 96.40 kB +1.35% 19.46 kB 19.72 kB
oss-stable-semver/react-server-dom-webpack/cjs/react-server-dom-webpack-server.node.unbundled.production.js +1.74% 95.73 kB 97.40 kB +1.37% 19.68 kB 19.95 kB
oss-stable/react-server-dom-webpack/cjs/react-server-dom-webpack-server.node.unbundled.production.js +1.74% 95.73 kB 97.40 kB +1.37% 19.68 kB 19.95 kB
oss-stable-semver/react-server-dom-turbopack/cjs/react-server-dom-turbopack-server.node.production.js +1.73% 96.73 kB 98.40 kB +1.31% 19.90 kB 20.16 kB
oss-stable/react-server-dom-turbopack/cjs/react-server-dom-turbopack-server.node.production.js +1.73% 96.73 kB 98.40 kB +1.31% 19.90 kB 20.16 kB
oss-stable-semver/react-server-dom-webpack/cjs/react-server-dom-webpack-server.node.production.js +1.72% 96.78 kB 98.45 kB +1.31% 19.91 kB 20.17 kB
oss-stable/react-server-dom-webpack/cjs/react-server-dom-webpack-server.node.production.js +1.72% 96.78 kB 98.45 kB +1.31% 19.91 kB 20.17 kB
oss-experimental/react-server-dom-turbopack/cjs/react-server-dom-turbopack-server.browser.production.js +1.71% 97.82 kB 99.49 kB +1.47% 19.88 kB 20.18 kB
oss-experimental/react-server-dom-webpack/cjs/react-server-dom-webpack-server.browser.production.js +1.70% 98.23 kB 99.90 kB +1.22% 20.04 kB 20.28 kB
oss-experimental/react-server-dom-turbopack/cjs/react-server-dom-turbopack-server.edge.production.js +1.68% 99.37 kB 101.03 kB +1.16% 20.30 kB 20.53 kB
oss-experimental/react-server-dom-webpack/cjs/react-server-dom-webpack-server.edge.production.js +1.68% 99.43 kB 101.10 kB +1.17% 20.31 kB 20.55 kB
oss-experimental/react-server-dom-webpack/cjs/react-server-dom-webpack-server.node.unbundled.production.js +1.66% 100.30 kB 101.96 kB +1.54% 20.44 kB 20.75 kB
oss-experimental/react-server-dom-turbopack/cjs/react-server-dom-turbopack-server.node.production.js +1.65% 101.29 kB 102.96 kB +1.22% 20.72 kB 20.97 kB
oss-experimental/react-server-dom-webpack/cjs/react-server-dom-webpack-server.node.production.js +1.65% 101.35 kB 103.02 kB +1.24% 20.73 kB 20.98 kB
oss-stable-semver/react-server-dom-parcel/cjs/react-server-dom-parcel-server.browser.development.js +1.33% 137.84 kB 139.68 kB +1.00% 25.69 kB 25.95 kB
oss-stable/react-server-dom-parcel/cjs/react-server-dom-parcel-server.browser.development.js +1.33% 137.84 kB 139.68 kB +1.00% 25.69 kB 25.95 kB
oss-stable-semver/react-server-dom-parcel/cjs/react-server-dom-parcel-server.edge.development.js +1.30% 141.67 kB 143.51 kB +0.99% 26.29 kB 26.55 kB
oss-stable/react-server-dom-parcel/cjs/react-server-dom-parcel-server.edge.development.js +1.30% 141.67 kB 143.51 kB +0.99% 26.29 kB 26.55 kB
oss-stable-semver/react-server-dom-esm/cjs/react-server-dom-esm-server.node.development.js +1.29% 142.26 kB 144.09 kB +0.98% 26.57 kB 26.83 kB
oss-stable/react-server-dom-esm/cjs/react-server-dom-esm-server.node.development.js +1.29% 142.26 kB 144.09 kB +0.98% 26.57 kB 26.83 kB
oss-stable-semver/react-server-dom-parcel/cjs/react-server-dom-parcel-server.node.development.js +1.29% 142.28 kB 144.12 kB +1.02% 26.55 kB 26.82 kB
oss-stable/react-server-dom-parcel/cjs/react-server-dom-parcel-server.node.development.js +1.29% 142.28 kB 144.12 kB +1.02% 26.55 kB 26.82 kB
oss-experimental/react-server-dom-parcel/cjs/react-server-dom-parcel-server.browser.development.js +1.27% 144.84 kB 146.68 kB +1.02% 26.89 kB 27.16 kB
oss-stable-semver/react-server-dom-turbopack/cjs/react-server-dom-turbopack-server.browser.development.js +1.26% 145.58 kB 147.42 kB +0.99% 26.99 kB 27.26 kB
oss-stable/react-server-dom-turbopack/cjs/react-server-dom-turbopack-server.browser.development.js +1.26% 145.58 kB 147.42 kB +0.99% 26.99 kB 27.26 kB
oss-stable-semver/react-server-dom-webpack/cjs/react-server-dom-webpack-server.browser.development.js +1.26% 146.12 kB 147.96 kB +1.00% 27.11 kB 27.39 kB
oss-stable/react-server-dom-webpack/cjs/react-server-dom-webpack-server.browser.development.js +1.26% 146.12 kB 147.96 kB +1.00% 27.11 kB 27.39 kB
oss-stable-semver/react-server-dom-webpack/cjs/react-server-dom-webpack-server.node.unbundled.development.js +1.23% 148.95 kB 150.79 kB +0.91% 27.59 kB 27.84 kB
oss-stable/react-server-dom-webpack/cjs/react-server-dom-webpack-server.node.unbundled.development.js +1.23% 148.95 kB 150.79 kB +0.91% 27.59 kB 27.84 kB
oss-experimental/react-server-dom-parcel/cjs/react-server-dom-parcel-server.edge.development.js +1.23% 149.30 kB 151.14 kB +0.94% 27.62 kB 27.89 kB
oss-stable-semver/react-server-dom-turbopack/cjs/react-server-dom-turbopack-server.edge.development.js +1.23% 149.45 kB 151.28 kB +0.99% 27.59 kB 27.86 kB
oss-stable/react-server-dom-turbopack/cjs/react-server-dom-turbopack-server.edge.development.js +1.23% 149.45 kB 151.28 kB +0.99% 27.59 kB 27.86 kB
oss-stable-semver/react-server-dom-webpack/cjs/react-server-dom-webpack-server.edge.development.js +1.23% 149.51 kB 151.35 kB +0.98% 27.60 kB 27.87 kB
oss-stable/react-server-dom-webpack/cjs/react-server-dom-webpack-server.edge.development.js +1.23% 149.51 kB 151.35 kB +0.98% 27.60 kB 27.87 kB
oss-experimental/react-server-dom-esm/cjs/react-server-dom-esm-server.node.development.js +1.23% 149.55 kB 151.39 kB +1.09% 27.84 kB 28.15 kB
oss-experimental/react-server-dom-parcel/cjs/react-server-dom-parcel-server.node.development.js +1.23% 149.57 kB 151.41 kB +0.94% 27.86 kB 28.12 kB
oss-stable-semver/react-server-dom-turbopack/cjs/react-server-dom-turbopack-server.node.development.js +1.22% 150.09 kB 151.93 kB +0.97% 27.86 kB 28.13 kB
oss-stable/react-server-dom-turbopack/cjs/react-server-dom-turbopack-server.node.development.js +1.22% 150.09 kB 151.93 kB +0.97% 27.86 kB 28.13 kB
oss-stable-semver/react-server-dom-webpack/cjs/react-server-dom-webpack-server.node.development.js +1.22% 150.16 kB 151.99 kB +0.97% 27.87 kB 28.14 kB
oss-stable/react-server-dom-webpack/cjs/react-server-dom-webpack-server.node.development.js +1.22% 150.16 kB 151.99 kB +0.97% 27.87 kB 28.14 kB
oss-experimental/react-server-dom-turbopack/cjs/react-server-dom-turbopack-server.browser.development.js +1.20% 152.58 kB 154.42 kB +0.97% 28.21 kB 28.49 kB
oss-experimental/react-server-dom-webpack/cjs/react-server-dom-webpack-server.browser.development.js +1.20% 153.12 kB 154.96 kB +1.02% 28.34 kB 28.62 kB
oss-experimental/react-server-dom-webpack/cjs/react-server-dom-webpack-server.node.unbundled.development.js +1.18% 156.24 kB 158.08 kB +1.00% 28.92 kB 29.21 kB
oss-experimental/react-server-dom-turbopack/cjs/react-server-dom-turbopack-server.edge.development.js +1.17% 157.08 kB 158.91 kB +0.92% 28.98 kB 29.25 kB
oss-experimental/react-server-dom-webpack/cjs/react-server-dom-webpack-server.edge.development.js +1.17% 157.14 kB 158.98 kB +0.92% 28.99 kB 29.25 kB
oss-experimental/react-server-dom-turbopack/cjs/react-server-dom-turbopack-server.node.development.js +1.17% 157.38 kB 159.22 kB +0.90% 29.21 kB 29.48 kB
oss-experimental/react-server-dom-webpack/cjs/react-server-dom-webpack-server.node.development.js +1.17% 157.45 kB 159.29 kB +0.90% 29.22 kB 29.49 kB

Generated by 🚫 dangerJS against ab472d7

@lubieowoce lubieowoce force-pushed the avoid-temporary-references-with-consumed-parts branch from be606fc to c83db4e Compare March 21, 2025 16:31
@lubieowoce lubieowoce force-pushed the avoid-temporary-references-with-consumed-parts branch from c83db4e to ab472d7 Compare March 21, 2025 16:41
@lubieowoce lubieowoce marked this pull request as ready for review March 21, 2025 16:50
@lubieowoce lubieowoce changed the title [Flight Reply] Don't create temporary references for object whose parts were consumed during serialization [Flight Reply] Don't create temporary references for objects whose parts were consumed during serialization Mar 21, 2025
Copy link
Collaborator

@sebmarkbage sebmarkbage left a comment

Choose a reason for hiding this comment

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

When I see something that leaks to the parent scope like this and trying to fix it with some stack context, that's a red flag to me. Because I've hit similar scenario in the past and it turns out to not be sufficient. Mainly because a tree isn't just one model. A model can contain references inside of it and some of those references may even be async.

For example, what happens if it's:

{ wrapper: Promise.resolve(iterator) }

We don't eagerly parse inside Promises but do that lazily. So we don't know what the deeply nested stuff will contain while parsing the wrapper. In fact, it might not even have streamed in yet.

In fact, ideally you should be able to return the temporary reference of the wrapper back to the client before even the iterator has been serialized on the client because it's still serializing. Such as if the Promise isn't resolved yet. Currently we block until the whole thing gets serialized but that's mainly historic because Safari didn't support streaming request bodies with fetch.

@sebmarkbage
Copy link
Collaborator

Even a partial solution might not be so great because it depends on implementation details and all data structures in between. So even if we make it work in some cases, there's a risk of us changing implementation details or the user deep down the line being able to change unrelated code along the path. That might make it not worth having a partial solution.

@sebmarkbage
Copy link
Collaborator

It's interesting that this also requires deduping. It's a good reminder that deduping isn't just about identity preservation and performance but it's also required to serialize a single one-shot iterator in multiple places. We don't have perfect deduping today but that's the goal at least.

Copy link
Collaborator

@sebmarkbage sebmarkbage left a comment

Choose a reason for hiding this comment

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

I think we should instead bad one-shot in this direction, which would make them temporary references only.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

Successfully merging this pull request may close these issues.

4 participants