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

Merged
merged 3 commits into from Dec 14, 2018

Conversation

Projects
None yet
7 participants
@acdlite
Copy link
Member

acdlite commented Dec 13, 2018

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

@sizebot

This comment has been minimized.

Copy link

sizebot commented Dec 13, 2018

ReactDOM: size: 🔺+0.7%, gzip: 🔺+0.8%

Details of bundled changes.

Comparing: 535804f...3b60eef

react-dom

File Filesize Diff Gzip Diff Prev Size Current Size Prev Gzip Current Gzip ENV
react-dom.development.js +0.3% +0.2% 724.65 KB 726.61 KB 167.54 KB 167.94 KB UMD_DEV
react-dom.production.min.js 🔺+0.7% 🔺+0.8% 97.81 KB 98.48 KB 31.85 KB 32.1 KB UMD_PROD
react-dom.profiling.min.js +0.7% +0.6% 100.8 KB 101.46 KB 32.56 KB 32.77 KB UMD_PROFILING
react-dom.development.js +0.3% +0.2% 719.7 KB 721.67 KB 166.14 KB 166.52 KB NODE_DEV
react-dom.production.min.js 🔺+0.7% 🔺+0.6% 97.81 KB 98.47 KB 31.39 KB 31.58 KB NODE_PROD
react-dom.profiling.min.js +0.7% +0.6% 100.89 KB 101.58 KB 32 KB 32.18 KB NODE_PROFILING
ReactDOM-dev.js +0.3% +0.2% 741.1 KB 743 KB 167.13 KB 167.53 KB FB_WWW_DEV
ReactDOM-prod.js 🔺+0.3% 🔺+0.6% 308.21 KB 309.08 KB 56.79 KB 57.13 KB FB_WWW_PROD
ReactDOM-profiling.js +0.4% +0.5% 315.1 KB 316.28 KB 58.22 KB 58.49 KB FB_WWW_PROFILING
react-dom-unstable-fire.development.js +0.3% +0.2% 724.93 KB 726.9 KB 167.65 KB 168.05 KB UMD_DEV
react-dom-unstable-fire.production.min.js 🔺+0.7% 🔺+0.8% 97.83 KB 98.49 KB 31.86 KB 32.1 KB UMD_PROD
react-dom-unstable-fire.profiling.min.js +0.7% +0.6% 100.81 KB 101.47 KB 32.57 KB 32.77 KB UMD_PROFILING
react-dom-unstable-fire.development.js +0.3% +0.2% 719.98 KB 721.96 KB 166.25 KB 166.64 KB NODE_DEV
react-dom-unstable-fire.production.min.js 🔺+0.7% 🔺+0.6% 97.82 KB 98.49 KB 31.4 KB 31.59 KB NODE_PROD
react-dom-unstable-fire.profiling.min.js +0.7% +0.6% 100.91 KB 101.59 KB 32.01 KB 32.19 KB NODE_PROFILING
ReactFire-dev.js +0.3% +0.2% 740.25 KB 742.15 KB 167.06 KB 167.46 KB FB_WWW_DEV
ReactFire-prod.js 🔺+0.3% 🔺+0.8% 296.8 KB 297.67 KB 54.36 KB 54.79 KB FB_WWW_PROD
ReactFire-profiling.js +0.4% +0.5% 303.62 KB 304.8 KB 55.84 KB 56.13 KB FB_WWW_PROFILING
react-dom-test-utils.development.js 0.0% -0.0% 44.87 KB 44.87 KB 12.3 KB 12.3 KB UMD_DEV
react-dom-test-utils.production.min.js 0.0% -0.1% 9.97 KB 9.97 KB 3.71 KB 3.71 KB UMD_PROD
react-dom-test-utils.development.js 0.0% -0.0% 44.59 KB 44.59 KB 12.24 KB 12.24 KB NODE_DEV
react-dom-test-utils.production.min.js 0.0% -0.0% 9.74 KB 9.74 KB 3.65 KB 3.65 KB NODE_PROD
react-dom-unstable-native-dependencies.development.js 0.0% -0.0% 60.61 KB 60.61 KB 15.92 KB 15.92 KB UMD_DEV
react-dom-unstable-native-dependencies.production.min.js 0.0% -0.1% 11.01 KB 11.01 KB 3.81 KB 3.81 KB UMD_PROD
react-dom-unstable-native-dependencies.development.js 0.0% -0.0% 60.29 KB 60.29 KB 15.79 KB 15.79 KB NODE_DEV
react-dom-unstable-native-dependencies.production.min.js 0.0% -0.1% 10.75 KB 10.75 KB 3.71 KB 3.71 KB NODE_PROD
react-dom-server.browser.development.js 0.0% -0.0% 123.89 KB 123.89 KB 33.09 KB 33.09 KB UMD_DEV
react-dom-server.browser.production.min.js 0.0% -0.0% 16.99 KB 16.99 KB 6.53 KB 6.52 KB UMD_PROD
react-dom-server.browser.development.js 0.0% -0.0% 120.02 KB 120.02 KB 32.16 KB 32.16 KB NODE_DEV
react-dom-server.browser.production.min.js 0.0% -0.0% 16.9 KB 16.9 KB 6.52 KB 6.52 KB NODE_PROD
ReactDOMServer-dev.js 0.0% -0.0% 121.2 KB 121.2 KB 31.79 KB 31.79 KB FB_WWW_DEV
ReactDOMServer-prod.js 0.0% -0.0% 44.42 KB 44.42 KB 10.3 KB 10.3 KB FB_WWW_PROD
react-dom-server.node.development.js 0.0% -0.0% 122.08 KB 122.08 KB 32.71 KB 32.7 KB NODE_DEV
react-dom-server.node.production.min.js 0.0% -0.0% 17.77 KB 17.77 KB 6.83 KB 6.83 KB NODE_PROD
react-dom-unstable-fizz.browser.development.js 0.0% -0.1% 3.63 KB 3.63 KB 1.44 KB 1.44 KB UMD_DEV
react-dom-unstable-fizz.browser.production.min.js 0.0% -0.3% 1.21 KB 1.21 KB 706 B 704 B UMD_PROD
react-dom-unstable-fizz.browser.development.js 0.0% -0.1% 3.45 KB 3.45 KB 1.39 KB 1.39 KB NODE_DEV
react-dom-unstable-fizz.browser.production.min.js 0.0% -0.3% 1.05 KB 1.05 KB 637 B 635 B NODE_PROD
react-dom-unstable-fizz.node.development.js 0.0% -0.1% 3.7 KB 3.7 KB 1.42 KB 1.42 KB NODE_DEV
react-dom-unstable-fizz.node.production.min.js 0.0% -0.4% 1.1 KB 1.1 KB 668 B 665 B NODE_PROD

react-art

File Filesize Diff Gzip Diff Prev Size Current Size Prev Gzip Current Gzip ENV
react-art.development.js +0.4% +0.4% 505.05 KB 507.02 KB 111.4 KB 111.8 KB UMD_DEV
react-art.production.min.js 🔺+0.7% 🔺+0.7% 89.98 KB 90.59 KB 27.67 KB 27.85 KB UMD_PROD
react-art.development.js +0.5% +0.4% 436.54 KB 438.52 KB 94.28 KB 94.67 KB NODE_DEV
react-art.production.min.js 🔺+1.1% 🔺+0.8% 54.95 KB 55.57 KB 17 KB 17.14 KB NODE_PROD
ReactART-dev.js +0.4% +0.4% 444.78 KB 446.68 KB 93.25 KB 93.64 KB FB_WWW_DEV
ReactART-prod.js 🔺+0.8% 🔺+0.9% 183.65 KB 185.16 KB 31.4 KB 31.7 KB FB_WWW_PROD

react-native-renderer

File Filesize Diff Gzip Diff Prev Size Current Size Prev Gzip Current Gzip ENV
ReactNativeRenderer-dev.js +0.3% +0.3% 569.25 KB 571.15 KB 123.75 KB 124.15 KB RN_FB_DEV
ReactNativeRenderer-prod.js 🔺+0.6% 🔺+0.9% 239.25 KB 240.71 KB 41.98 KB 42.35 KB RN_FB_PROD
ReactNativeRenderer-profiling.js +0.7% +0.9% 245.12 KB 246.89 KB 43.35 KB 43.75 KB RN_FB_PROFILING
ReactNativeRenderer-dev.js +0.3% +0.3% 569.17 KB 571.06 KB 123.72 KB 124.11 KB RN_OSS_DEV
ReactNativeRenderer-prod.js 🔺+0.7% 🔺+0.9% 224.38 KB 225.84 KB 38.97 KB 39.33 KB RN_OSS_PROD
ReactNativeRenderer-profiling.js +0.8% +1.0% 230.12 KB 231.89 KB 40.31 KB 40.72 KB RN_OSS_PROFILING
ReactFabric-dev.js +0.3% +0.3% 560.1 KB 562 KB 121.45 KB 121.85 KB RN_FB_DEV
ReactFabric-prod.js 🔺+0.2% 🔺+0.3% 233.4 KB 233.92 KB 40.8 KB 40.9 KB RN_FB_PROD
ReactFabric-profiling.js +0.4% +0.3% 238.36 KB 239.24 KB 42.16 KB 42.26 KB RN_FB_PROFILING
ReactFabric-dev.js +0.3% +0.3% 560.01 KB 561.91 KB 121.4 KB 121.8 KB RN_OSS_DEV
ReactFabric-prod.js 🔺+0.2% 🔺+0.3% 218.55 KB 219.07 KB 37.72 KB 37.84 KB RN_OSS_PROD
ReactFabric-profiling.js +0.4% +0.3% 223.31 KB 224.19 KB 39.13 KB 39.25 KB RN_OSS_PROFILING

react-test-renderer

File Filesize Diff Gzip Diff Prev Size Current Size Prev Gzip Current Gzip ENV
react-test-renderer.development.js +0.4% +0.4% 449.77 KB 451.73 KB 97.06 KB 97.44 KB UMD_DEV
react-test-renderer.production.min.js 🔺+1.2% 🔺+1.5% 56.28 KB 56.97 KB 17.27 KB 17.53 KB UMD_PROD
react-test-renderer.development.js +0.4% +0.4% 444.72 KB 446.69 KB 95.84 KB 96.22 KB NODE_DEV
react-test-renderer.production.min.js 🔺+1.2% 🔺+1.4% 55.94 KB 56.64 KB 17.13 KB 17.37 KB NODE_PROD
ReactTestRenderer-dev.js +0.4% +0.4% 453.15 KB 455.04 KB 95.12 KB 95.52 KB FB_WWW_DEV
react-test-renderer-shallow.development.js 0.0% -0.0% 25.88 KB 25.88 KB 7.06 KB 7.05 KB UMD_DEV
react-test-renderer-shallow.production.min.js 0.0% -0.0% 7.31 KB 7.31 KB 2.39 KB 2.39 KB UMD_PROD
react-test-renderer-shallow.development.js 0.0% -0.0% 20.18 KB 20.18 KB 5.61 KB 5.61 KB NODE_DEV
react-test-renderer-shallow.production.min.js 0.0% -0.1% 7.96 KB 7.96 KB 2.64 KB 2.64 KB NODE_PROD

react-reconciler

File Filesize Diff Gzip Diff Prev Size Current Size Prev Gzip Current Gzip ENV
react-reconciler.development.js +0.5% +0.4% 434.37 KB 436.35 KB 92.79 KB 93.2 KB NODE_DEV
react-reconciler.production.min.js 🔺+1.1% 🔺+0.9% 56.07 KB 56.71 KB 16.83 KB 16.99 KB NODE_PROD
react-reconciler-persistent.development.js +0.5% +0.5% 432.75 KB 434.73 KB 92.14 KB 92.55 KB NODE_DEV
react-reconciler-persistent.production.min.js 🔺+1.1% 🔺+0.9% 56.08 KB 56.72 KB 16.84 KB 16.99 KB NODE_PROD
react-reconciler-reflection.development.js 0.0% -0.0% 15.4 KB 15.4 KB 4.83 KB 4.83 KB NODE_DEV
react-reconciler-reflection.production.min.js 0.0% -0.1% 2.56 KB 2.56 KB 1.13 KB 1.13 KB NODE_PROD

Generated by 🚫 dangerJS

@threepointone
Copy link
Contributor

threepointone left a comment

a learning review for me.

@acdlite acdlite force-pushed the acdlite:memoize-promise-listeners branch 2 times, most recently from 891fcf2 to 79eb26e Dec 14, 2018

);
// runPlaceholderTests('ReactSuspensePlaceholder (mutation)', () =>
// require('react-noop-renderer'),
// );

This comment has been minimized.

@sebmarkbage

sebmarkbage Dec 14, 2018

Member

Why disable these?

This comment has been minimized.

@acdlite

acdlite Dec 14, 2018

Member

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 :facepalm:

This comment has been minimized.

@acdlite

acdlite Dec 14, 2018

Member

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.

This comment has been minimized.

@sebmarkbage

sebmarkbage Dec 14, 2018

Member

I thought we wanted to do this work in the passive effect? Is there a reason not to?

This comment has been minimized.

@acdlite

acdlite Dec 14, 2018

Member

Only that it seemed like a riskier change. I'll add a follow-up.

@sebmarkbage

This comment has been minimized.

Copy link
Member

sebmarkbage commented Dec 14, 2018

You still need to add the lint config to a few more places like RN.

acdlite added some commits Dec 13, 2018

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 #14220

@acdlite acdlite force-pushed the acdlite:memoize-promise-listeners branch from 000a8dd to 3b60eef Dec 14, 2018

@acdlite acdlite merged commit 4a10721 into facebook:master Dec 14, 2018

1 check passed

ci/circleci Your tests passed on CircleCI!
Details
@Jessidhia

This comment has been minimized.

Copy link

Jessidhia commented Dec 19, 2018

"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?)

@acdlite

This comment has been minimized.

Copy link
Member

acdlite commented Dec 23, 2018

@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.

@gaearon

This comment has been minimized.

Copy link
Member

gaearon commented Jan 14, 2019

Is there any chance that "optional" dependency on WeakMap and WeakSet somehow changes behavior when they're not available? There's been two reported issues with Suspense behaving weirdly on IE11 since this PR.

#14570
#14583

@threepointone

This comment has been minimized.

Copy link
Contributor

threepointone commented Jan 15, 2019

^ Just dropping a note here that the above issues were because of a bad polyfill, and have been 'resolved'. This PR is fine.

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