-
Notifications
You must be signed in to change notification settings - Fork 47.1k
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
Memoize promise listeners to prevent exponential growth #14429
Conversation
ReactDOM: size: 🔺+0.7%, gzip: 🔺+0.8% Details of bundled changes.Comparing: 535804f...3b60eef react-dom
react-art
react-native-renderer
react-test-renderer
react-reconciler
Generated by 🚫 dangerJS |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
a learning review for me.
891fcf2
to
79eb26e
Compare
); | ||
// runPlaceholderTests('ReactSuspensePlaceholder (mutation)', () => | ||
// require('react-noop-renderer'), | ||
// ); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Why disable these?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Commented it out while debugging, meant to add back. Although now I just noticed... I screwed up when I converted this to use the test renderer. It's not actually testing anything in concurrent mode anymore 🤦
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I'll fix that in a separate PR since it's unrelated
|
||
// If this boundary just timed out, then it will have a set of thenables. | ||
// For each thenable, attach a listener so that when it resolves, React | ||
// attempts to re-render the boundary in the primary (pre-timeout) state. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I thought we wanted to do this work in the passive effect? Is there a reason not to?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Only that it seemed like a riskier change. I'll add a follow-up.
You still need to add the lint config to a few more places like RN. |
Previously, React would attach a new listener every time a promise is thrown, regardless of whether the same listener was already attached during a previous render. Because React attempts to render every time a promise resolves, the number of listeners grows quickly. This was especially bad in synchronous mode because the renders that happen when the promise pings are not batched together. So if a single promise has multiple listeners for the same root, there will be multiple renders, which in turn results in more listeners being added to the remaining unresolved promises. This results in exponential growth in the number of listeners with respect to the number of IO-bound components in a single render. Fixes facebook#14220
000a8dd
to
3b60eef
Compare
"Because React attempts to render every time a promise resolves" ah, so it's intentional. I thought it was a bug when I encountered this in test code (I thought React would wait for all thrown Promises to resolve before attempting to rerender a Suspense. Won't this just cause another suspend again?) |
@Kovensky Every time a promise pings it means some part of the tree has been unblocked, so it’s worth rendering again, even before all the promises resolve, to fire the nested requests. |
^ Just dropping a note here that the above issues were because of a bad polyfill, and have been 'resolved'. This PR is fine. |
* Memoize promise listeners to prevent exponential growth Previously, React would attach a new listener every time a promise is thrown, regardless of whether the same listener was already attached during a previous render. Because React attempts to render every time a promise resolves, the number of listeners grows quickly. This was especially bad in synchronous mode because the renders that happen when the promise pings are not batched together. So if a single promise has multiple listeners for the same root, there will be multiple renders, which in turn results in more listeners being added to the remaining unresolved promises. This results in exponential growth in the number of listeners with respect to the number of IO-bound components in a single render. Fixes facebook#14220 * Memoize on the root and Suspense fiber instead of on the promise * Add TODO to fix persistent mode tests
* Memoize promise listeners to prevent exponential growth Previously, React would attach a new listener every time a promise is thrown, regardless of whether the same listener was already attached during a previous render. Because React attempts to render every time a promise resolves, the number of listeners grows quickly. This was especially bad in synchronous mode because the renders that happen when the promise pings are not batched together. So if a single promise has multiple listeners for the same root, there will be multiple renders, which in turn results in more listeners being added to the remaining unresolved promises. This results in exponential growth in the number of listeners with respect to the number of IO-bound components in a single render. Fixes facebook#14220 * Memoize on the root and Suspense fiber instead of on the promise * Add TODO to fix persistent mode tests
Previously, React would attach a new listener every time a promise is thrown, regardless of whether the same listener was already attached during a previous render. Because React attempts to render every time a promise resolves, the number of listeners grows quickly.
This was especially bad in synchronous mode because the renders that happen when the promise pings are not batched together. So if a single promise has multiple listeners for the same root, there will be multiple renders, which in turn results in more listeners being added to the remaining unresolved promises. This results in exponential growth in the number of listeners with respect to the number of IO-bound components in a single render.
Fixes #14220