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

Check for store mutations before commit #22290

Merged
merged 3 commits into from
Sep 13, 2021

Conversation

acdlite
Copy link
Collaborator

@acdlite acdlite commented Sep 10, 2021

When a store is read for the first time, or when subscribe or getSnapshot changes, during a concurrent render, we have to check at the end of the render phase whether the store was mutated by a concurrent event.

In the userspace shim, we perform this check in a layout effect, and patch up any inconsistencies by scheduling another render + commit. However, even though we patch them up in the next render, the parent layout effects that fire in the original render will still observe an inconsistent tree.

In the native implementation, we can instead check for inconsistencies right after the root is completed, before entering the commit phase. If we do detect a mutation, we can discard the tree and re-render before firing any effects. The re-render is synchronous to block further concurrent mutations (which is also what we do to recover from tearing bugs that result in an error). After the synchronous re-render, we can assume the tree the tree is consistent and continue with the normal algorithm for finishing a completed root (i.e. either suspend or commit).

The result is that layout effects will always observe a consistent tree.

Because we already track `getSnapshot` and `value` on the store
instance, we don't need to also track them as effect dependencies. And
because the effect doesn't require any clean-up, we don't need to track
a `destroy` function.

So, we don't need to store any additional state for this effect. We can
call `pushEffect` directly, and only during renders where something
has changed.

This saves some memory, but my main motivation is because I plan to use
this same logic to schedule a pre-commit consistency check. (See the
inline comments for more details.)
@facebook-github-bot facebook-github-bot added CLA Signed React Core Team Opened by a member of the React Core Team labels Sep 10, 2021
@sizebot
Copy link

sizebot commented Sep 10, 2021

Comparing: 0fd195f...6ca37ef

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.min.js +0.80% 128.27 kB 129.30 kB +0.71% 40.93 kB 41.22 kB
oss-experimental/react-dom/cjs/react-dom.production.min.js +0.78% 131.09 kB 132.12 kB +0.73% 41.85 kB 42.16 kB
facebook-www/ReactDOM-prod.classic.js +0.80% 407.09 kB 410.35 kB +0.73% 75.40 kB 75.95 kB
facebook-www/ReactDOM-prod.modern.js +0.82% 395.65 kB 398.91 kB +0.74% 73.69 kB 74.24 kB
facebook-www/ReactDOMForked-prod.classic.js +0.80% 407.09 kB 410.35 kB +0.73% 75.40 kB 75.95 kB

Significant size changes

Includes any change greater than 0.2%:

Expand to show
Name +/- Base Current +/- gzip Base gzip Current gzip
facebook-react-native/react-test-renderer/cjs/ReactTestRenderer-prod.js +1.51% 234.60 kB 238.13 kB +1.35% 43.06 kB 43.64 kB
facebook-react-native/react-test-renderer/cjs/ReactTestRenderer-profiling.js +1.42% 249.22 kB 252.76 kB +1.63% 45.32 kB 46.05 kB
oss-stable-semver/react-test-renderer/cjs/react-test-renderer.production.min.js +1.30% 78.23 kB 79.25 kB +1.23% 24.24 kB 24.54 kB
oss-stable/react-test-renderer/cjs/react-test-renderer.production.min.js +1.30% 78.23 kB 79.25 kB +1.23% 24.24 kB 24.54 kB
oss-stable-semver/react-test-renderer/umd/react-test-renderer.production.min.js +1.29% 78.45 kB 79.46 kB +1.12% 24.62 kB 24.89 kB
oss-stable/react-test-renderer/umd/react-test-renderer.production.min.js +1.29% 78.45 kB 79.46 kB +1.12% 24.62 kB 24.89 kB
react-native/implementations/ReactFabric-prod.fb.js +1.27% 278.21 kB 281.75 kB +1.17% 50.06 kB 50.65 kB
facebook-www/ReactART-prod.modern.js +1.27% 256.30 kB 259.56 kB +1.27% 45.88 kB 46.46 kB
oss-experimental/react-test-renderer/cjs/react-test-renderer.production.min.js +1.26% 80.61 kB 81.63 kB +1.11% 25.01 kB 25.29 kB
oss-stable-semver/react-art/cjs/react-art.production.min.js +1.26% 81.37 kB 82.39 kB +1.06% 25.26 kB 25.53 kB
oss-stable/react-art/cjs/react-art.production.min.js +1.26% 81.37 kB 82.39 kB +1.06% 25.26 kB 25.53 kB
react-native/implementations/ReactNativeRenderer-prod.fb.js +1.26% 282.08 kB 285.62 kB +1.20% 50.96 kB 51.57 kB
oss-experimental/react-test-renderer/umd/react-test-renderer.production.min.js +1.24% 80.82 kB 81.83 kB +1.35% 25.39 kB 25.73 kB
facebook-www/ReactART-prod.classic.js +1.23% 263.97 kB 267.22 kB +1.26% 47.12 kB 47.71 kB
react-native/implementations/ReactFabric-prod.js +1.22% 270.91 kB 274.22 kB +1.04% 48.74 kB 49.24 kB
oss-experimental/react-art/cjs/react-art.production.min.js +1.22% 83.76 kB 84.78 kB +1.13% 26.04 kB 26.34 kB
react-native/implementations/ReactFabric-profiling.fb.js +1.20% 296.20 kB 299.76 kB +1.19% 53.07 kB 53.70 kB
react-native/implementations/ReactNativeRenderer-profiling.fb.js +1.18% 300.04 kB 303.59 kB +1.33% 53.85 kB 54.57 kB
react-native/implementations/ReactNativeRenderer-prod.js +1.18% 279.47 kB 282.78 kB +1.11% 50.35 kB 50.91 kB
react-native/implementations/ReactFabric-profiling.js +1.15% 288.87 kB 292.19 kB +1.23% 51.60 kB 52.24 kB
oss-stable-semver/react-reconciler/cjs/react-reconciler.production.min.js +1.13% 90.45 kB 91.47 kB +0.99% 27.78 kB 28.06 kB
oss-stable/react-reconciler/cjs/react-reconciler.production.min.js +1.13% 90.45 kB 91.47 kB +0.99% 27.78 kB 28.06 kB
react-native/implementations/ReactNativeRenderer-profiling.js +1.12% 297.43 kB 300.75 kB +1.23% 53.22 kB 53.88 kB
oss-experimental/react-reconciler/cjs/react-reconciler.production.min.js +1.10% 92.90 kB 93.93 kB +1.10% 28.55 kB 28.86 kB
oss-stable-semver/react-reconciler/cjs/react-reconciler.profiling.min.js +1.04% 98.22 kB 99.24 kB +0.96% 30.11 kB 30.40 kB
oss-stable/react-reconciler/cjs/react-reconciler.profiling.min.js +1.04% 98.22 kB 99.24 kB +0.96% 30.11 kB 30.40 kB
oss-experimental/react-reconciler/cjs/react-reconciler.profiling.min.js +1.01% 100.69 kB 101.71 kB +0.91% 30.87 kB 31.15 kB
oss-stable-semver/react-test-renderer/cjs/react-test-renderer.development.js +0.93% 604.16 kB 609.80 kB +0.90% 131.95 kB 133.14 kB
oss-stable/react-test-renderer/cjs/react-test-renderer.development.js +0.93% 604.16 kB 609.80 kB +0.90% 131.95 kB 133.14 kB
oss-stable-semver/react-test-renderer/umd/react-test-renderer.development.js +0.93% 633.66 kB 639.57 kB +0.94% 133.34 kB 134.59 kB
oss-stable/react-test-renderer/umd/react-test-renderer.development.js +0.93% 633.66 kB 639.57 kB +0.94% 133.34 kB 134.59 kB
facebook-react-native/react-test-renderer/cjs/ReactTestRenderer-dev.js +0.92% 614.81 kB 620.46 kB +0.90% 132.96 kB 134.15 kB
oss-experimental/react-test-renderer/cjs/react-test-renderer.development.js +0.91% 620.01 kB 625.64 kB +0.89% 135.31 kB 136.51 kB
facebook-www/ReactTestRenderer-dev.classic.js +0.91% 626.23 kB 631.92 kB +0.90% 134.16 kB 135.36 kB
facebook-www/ReactTestRenderer-dev.modern.js +0.91% 626.24 kB 631.93 kB +0.90% 134.17 kB 135.37 kB
oss-experimental/react-test-renderer/umd/react-test-renderer.development.js +0.91% 650.35 kB 656.26 kB +0.93% 136.73 kB 138.01 kB
facebook-www/ReactDOMTesting-prod.modern.js +0.88% 384.15 kB 387.54 kB +0.78% 72.95 kB 73.52 kB
oss-stable-semver/react-art/umd/react-art.production.min.js +0.87% 117.29 kB 118.31 kB +0.89% 36.45 kB 36.78 kB
oss-stable/react-art/umd/react-art.production.min.js +0.87% 117.29 kB 118.31 kB +0.89% 36.45 kB 36.78 kB
oss-stable-semver/react-art/cjs/react-art.development.js +0.86% 654.00 kB 659.64 kB +0.82% 141.71 kB 142.87 kB
oss-stable/react-art/cjs/react-art.development.js +0.86% 654.00 kB 659.64 kB +0.82% 141.71 kB 142.87 kB
oss-experimental/react-art/umd/react-art.production.min.js +0.85% 119.68 kB 120.70 kB +0.92% 37.18 kB 37.52 kB
facebook-www/ReactDOMTesting-prod.classic.js +0.85% 397.41 kB 400.79 kB +0.76% 75.01 kB 75.59 kB
oss-experimental/react-art/cjs/react-art.development.js +0.84% 669.88 kB 675.52 kB +0.83% 145.09 kB 146.30 kB
facebook-www/ReactDOM-prod.modern.js +0.82% 395.65 kB 398.91 kB +0.74% 73.69 kB 74.24 kB
facebook-www/ReactDOMForked-prod.modern.js +0.82% 395.65 kB 398.91 kB +0.74% 73.70 kB 74.24 kB
facebook-www/ReactART-dev.modern.js +0.80% 707.86 kB 713.54 kB +0.80% 151.10 kB 152.31 kB
facebook-www/ReactDOM-prod.classic.js +0.80% 407.09 kB 410.35 kB +0.73% 75.40 kB 75.95 kB
facebook-www/ReactDOMForked-prod.classic.js +0.80% 407.09 kB 410.35 kB +0.73% 75.40 kB 75.95 kB
react-native/implementations/ReactFabric-dev.js +0.80% 705.56 kB 711.20 kB +0.80% 152.81 kB 154.04 kB
oss-stable-semver/react-reconciler/cjs/react-reconciler.development.js +0.80% 705.16 kB 710.80 kB +0.84% 149.86 kB 151.13 kB
oss-stable/react-reconciler/cjs/react-reconciler.development.js +0.80% 705.16 kB 710.80 kB +0.84% 149.86 kB 151.13 kB
oss-stable-semver/react-dom/cjs/react-dom.production.min.js +0.80% 128.27 kB 129.30 kB +0.71% 40.93 kB 41.22 kB
oss-stable/react-dom/cjs/react-dom.production.min.js +0.80% 128.27 kB 129.30 kB +0.71% 40.93 kB 41.22 kB
oss-stable-semver/react-dom/umd/react-dom.production.min.js +0.79% 128.42 kB 129.44 kB +0.71% 41.69 kB 41.99 kB
oss-stable/react-dom/umd/react-dom.production.min.js +0.79% 128.42 kB 129.44 kB +0.71% 41.69 kB 41.99 kB
facebook-www/ReactART-dev.classic.js +0.79% 718.14 kB 723.82 kB +0.82% 153.20 kB 154.46 kB
oss-experimental/react-reconciler/cjs/react-reconciler.development.js +0.78% 721.23 kB 726.87 kB +0.81% 153.30 kB 154.54 kB
oss-experimental/react-dom/cjs/react-dom.production.min.js +0.78% 131.09 kB 132.12 kB +0.73% 41.85 kB 42.16 kB
react-native/implementations/ReactNativeRenderer-dev.js +0.78% 722.70 kB 728.35 kB +0.78% 156.74 kB 157.96 kB
oss-stable-semver/react-art/umd/react-art.development.js +0.78% 757.41 kB 763.31 kB +0.83% 160.04 kB 161.37 kB
oss-stable/react-art/umd/react-art.development.js +0.78% 757.41 kB 763.31 kB +0.83% 160.04 kB 161.37 kB
oss-experimental/react-dom/umd/react-dom.production.min.js +0.78% 131.19 kB 132.22 kB +0.65% 42.65 kB 42.93 kB
react-native/implementations/ReactFabric-dev.fb.js +0.78% 727.55 kB 733.20 kB +0.76% 157.16 kB 158.36 kB
facebook-www/ReactDOM-profiling.modern.js +0.78% 421.90 kB 425.17 kB +0.75% 78.19 kB 78.77 kB
facebook-www/ReactDOMForked-profiling.modern.js +0.78% 421.90 kB 425.17 kB +0.75% 78.19 kB 78.77 kB
oss-experimental/react-art/umd/react-art.development.js +0.76% 774.13 kB 780.04 kB +0.81% 163.47 kB 164.78 kB
react-native/implementations/ReactNativeRenderer-dev.fb.js +0.76% 742.68 kB 748.33 kB +0.75% 160.55 kB 161.75 kB
facebook-www/ReactDOM-profiling.classic.js +0.76% 433.39 kB 436.66 kB +0.70% 79.90 kB 80.46 kB
facebook-www/ReactDOMForked-profiling.classic.js +0.76% 433.39 kB 436.66 kB +0.70% 79.90 kB 80.47 kB
oss-stable-semver/react-dom/cjs/react-dom.profiling.min.js +0.75% 135.94 kB 136.97 kB +0.72% 43.24 kB 43.55 kB
oss-stable/react-dom/cjs/react-dom.profiling.min.js +0.75% 135.94 kB 136.97 kB +0.72% 43.24 kB 43.55 kB
oss-stable-semver/react-dom/umd/react-dom.profiling.min.js +0.75% 136.05 kB 137.07 kB +0.76% 44.06 kB 44.39 kB
oss-stable/react-dom/umd/react-dom.profiling.min.js +0.75% 136.05 kB 137.07 kB +0.76% 44.06 kB 44.39 kB
oss-experimental/react-dom/cjs/react-dom.profiling.min.js +0.74% 138.78 kB 139.81 kB +0.63% 44.30 kB 44.58 kB
oss-experimental/react-dom/umd/react-dom.profiling.min.js +0.74% 138.84 kB 139.87 kB +0.80% 44.93 kB 45.29 kB
facebook-www/ReactDOMTesting-dev.modern.js +0.60% 946.24 kB 951.93 kB +0.58% 212.89 kB 214.12 kB
facebook-www/ReactDOMTesting-dev.classic.js +0.58% 973.31 kB 979.00 kB +0.56% 218.53 kB 219.76 kB
oss-stable-semver/react-dom/cjs/react-dom.development.js +0.58% 971.53 kB 977.17 kB +0.60% 218.60 kB 219.91 kB
oss-stable/react-dom/cjs/react-dom.development.js +0.58% 971.53 kB 977.17 kB +0.60% 218.60 kB 219.91 kB
oss-stable-semver/react-dom/umd/react-dom.development.js +0.58% 1,020.73 kB 1,026.64 kB +0.58% 221.23 kB 222.51 kB
oss-stable/react-dom/umd/react-dom.development.js +0.58% 1,020.73 kB 1,026.64 kB +0.58% 221.23 kB 222.51 kB
oss-experimental/react-dom/cjs/react-dom.development.js +0.57% 989.37 kB 995.01 kB +0.57% 222.36 kB 223.62 kB
oss-experimental/react-dom/umd/react-dom.development.js +0.57% 1,039.54 kB 1,045.45 kB +0.58% 224.94 kB 226.24 kB
facebook-www/ReactDOMForked-dev.modern.js +0.54% 1,044.17 kB 1,049.86 kB +0.54% 231.88 kB 233.13 kB
facebook-www/ReactDOM-dev.modern.js +0.54% 1,044.17 kB 1,049.86 kB +0.54% 231.88 kB 233.13 kB
facebook-www/ReactDOMForked-dev.classic.js +0.53% 1,068.62 kB 1,074.30 kB +0.54% 236.85 kB 238.12 kB
facebook-www/ReactDOM-dev.classic.js +0.53% 1,068.62 kB 1,074.30 kB +0.54% 236.85 kB 238.13 kB

Generated by 🚫 dangerJS against 6ca37ef

@acdlite acdlite force-pushed the check-store-mutations-before-commit branch 4 times, most recently from 2008a36 to fedf5e7 Compare September 10, 2021 18:50
Lanes that are blocking (SyncLane, and DefaultLane inside a blocking-
by-default root) are always blocking for a given root. Whereas expired
lanes can expire while the render phase is already in progress.

I want to check if a lane is blocking without checking whether it
expired, so I split `shouldTimeSlice` into two separate functions.

I'll use this in the next step.
When a store is read for the first time, or when `subscribe` or
`getSnapshot` changes, during a concurrent render, we have to check
at the end of the render phase whether the store was mutated by
an concurrent event.

In the userspace shim, we perform this check in a layout effect, and
patch up any inconsistencies by scheduling another render + commit.
However, even though we patch them up in the next render, the parent
layout effects that fire in the original render will still observe an
inconsistent tree.

In the native implementation, we can instead check for inconsistencies
right after the root is completed, before entering the commit phase. If
we do detect a mutaiton, we can discard the tree and re-render before
firing any effects. The re-render is synchronous to block further
concurrent mutations (which is also what we do to recover from tearing
bugs that result in an error). After the synchronous re-render, we can
assume the tree the tree is consistent and continue with the normal
algorithm for finishing a completed root (i.e. either suspend
or commit).

The result is that layout effects will always observe a consistent tree.
@acdlite acdlite force-pushed the check-store-mutations-before-commit branch from fedf5e7 to 6ca37ef Compare September 10, 2021 19:32
@acdlite acdlite marked this pull request as ready for review September 10, 2021 20:38
@acdlite acdlite merged commit 33226fa into facebook:main Sep 13, 2021
facebook-github-bot pushed a commit to facebook/react-native that referenced this pull request Sep 22, 2021
Summary:
This sync includes the following changes:
- **[f4ac680c7](facebook/react@f4ac680c7 )**: Fixed broken build script --unsafe-partial flag ([#22324](facebook/react#22324)) //<Brian Vaughn>//
- **[67222f044](facebook/react@67222f044 )**: [Experiment] Warn if callback ref returns a function ([#22313](facebook/react#22313)) //<Dan Abramov>//
- **[263cfa6ec](facebook/react@263cfa6ec )**: [Experimental] Add useInsertionEffect ([#21913](facebook/react#21913)) //<Ricky>//
- **[806aaa2e2](facebook/react@806aaa2e2 )**: [useSES shim] Import prefixed native API ([#22310](facebook/react#22310)) //<Andrew Clark>//
- **[fd5e01c2e](facebook/react@fd5e01c2e )**: [useSES/extra] Reuse old selection if possible ([#22307](facebook/react#22307)) //<Andrew Clark>//
- **[33226fada](facebook/react@33226fada )**: Check for store mutations before commit ([#22290](facebook/react#22290)) //<Andrew Clark>//
- **[86c7ca70a](facebook/react@86c7ca70a )**: Fix link ([#22296](facebook/react#22296)) //<Konstantin Popov>//
- **[0fd195f29](facebook/react@0fd195f29 )**: update error message to include useLayoutEffect or useEffect on bad e… ([#22279](facebook/react#22279)) //<salazarm>//
- **[8f96c6b2a](facebook/react@8f96c6b2a )**: [Bugfix] Prevent infinite update loop caused by a synchronous update in a passive effect ([#22277](facebook/react#22277)) //<Andrew Clark>//
- **[4ce89a58d](facebook/react@4ce89a58d )**: Test bad useEffect return value with noop-renderer ([#22258](facebook/react#22258)) //<Sebastian Silbermann>//
- **[a3fde2358](facebook/react@a3fde2358 )**: Detect subscriptions wrapped in startTransition ([#22271](facebook/react#22271)) //<salazarm>//

Changelog:
[General][Changed] - React Native sync for revisions 95d762e...e8feb11

jest_e2e[run_all_tests]

Reviewed By: rickhanlonii

Differential Revision: D30966369

fbshipit-source-id: 6c88e591005deb1fd93493628ef4695add49186c
zhengjitf pushed a commit to zhengjitf/react that referenced this pull request Apr 15, 2022
* [useSyncExternalStore] Remove extra hook object

Because we already track `getSnapshot` and `value` on the store
instance, we don't need to also track them as effect dependencies. And
because the effect doesn't require any clean-up, we don't need to track
a `destroy` function.

So, we don't need to store any additional state for this effect. We can
call `pushEffect` directly, and only during renders where something
has changed.

This saves some memory, but my main motivation is because I plan to use
this same logic to schedule a pre-commit consistency check. (See the
inline comments for more details.)

* Split shouldTimeSlice into two separate functions

Lanes that are blocking (SyncLane, and DefaultLane inside a blocking-
by-default root) are always blocking for a given root. Whereas expired
lanes can expire while the render phase is already in progress.

I want to check if a lane is blocking without checking whether it
expired, so I split `shouldTimeSlice` into two separate functions.

I'll use this in the next step.

* Check for store mutations before commit

When a store is read for the first time, or when `subscribe` or
`getSnapshot` changes, during a concurrent render, we have to check
at the end of the render phase whether the store was mutated by
an concurrent event.

In the userspace shim, we perform this check in a layout effect, and
patch up any inconsistencies by scheduling another render + commit.
However, even though we patch them up in the next render, the parent
layout effects that fire in the original render will still observe an
inconsistent tree.

In the native implementation, we can instead check for inconsistencies
right after the root is completed, before entering the commit phase. If
we do detect a mutaiton, we can discard the tree and re-render before
firing any effects. The re-render is synchronous to block further
concurrent mutations (which is also what we do to recover from tearing
bugs that result in an error). After the synchronous re-render, we can
assume the tree the tree is consistent and continue with the normal
algorithm for finishing a completed root (i.e. either suspend
or commit).

The result is that layout effects will always observe a consistent tree.
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