-
Notifications
You must be signed in to change notification settings - Fork 47.2k
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
Fix: Move destroy
field to shared instance object
#26561
Conversation
Comparing: 4a1cc2d...94e2375 Critical size changesIncludes critical production bundles, as well as any change greater than 2%:
Significant size changesIncludes any change greater than 0.2%: (No significant changes) |
@@ -2190,7 +2192,8 @@ function commitDeletionEffectsOnFiber( | |||
|
|||
let effect = firstEffect; | |||
do { | |||
const {destroy, tag} = effect; | |||
const tag = effect.tag; | |||
const destroy = effect.inst.destroy; |
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.
Btw why do we not clear out the destroy field here?
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 guess it doesn't really matter here because the fiber is being deleted but we can for consistency
Mind adding Jan and Rick to my test commit as coauthors? |
Co-authored-by: Rick Hanlon <rickhanlonii@gmail.com> Co-authored-by: Jan Kassens <jan@kassens.net>
This fixes the "double free" bug illustrated by the regression test added in the previous commit. The underlying issue is that `effect.destroy` field is a mutable field but we read it during render. This is a concurrency bug — if we had a borrow checker, it would not allow this. It's rare in practice today because the field is updated during the commit phase, which takes a lock on the fiber tree until all the effects have fired. But it's still theoretically wrong because you can have multiple Fiber copies each with their own reference to a single destroy function, and indeed we discovered in production a scenario where this happens via our current APIs. In the future these types of scenarios will be much more common because we will introduce features where effects may run concurrently with the render phase — i.e. an imperative `hide` method that synchronously hides a React tree and unmounts all its effects without entering the render phase, and without interrupting a render phase that's already in progress. A future version of React may also be able to run the entire commit phase concurrently with a subsequent render phase. We can't do this now because our data structures are not fully thread safe (see: the Fiber alternate model) but we should be able to do this in the future. The fix I've introduced in this commit is to move the `destroy` field to a separate object. The effect "instance" is a shared object that remains the same for the entire lifetime of an effect. In Rust terms, a RefCell. The field is `undefined` if the effect is unmounted, or if the effect ran but is not stateful. We don't explicitly track whether the effect is mounted or unmounted because that can be inferred by the hiddenness of the fiber in the tree, i.e. whether there is a hidden Offscreen fiber above it. It's unfortunate that this is stored on a separate object, because it adds more memory per effect instance, but it's conceptually sound. I think there's likely a better data structure we could use for effects; perhaps just one array of effect instances per fiber. But I think this is OK for now despite the additional memory and we can follow up with performance optimizations later.
788a3ba
to
94e2375
Compare
This fixes the "double free" bug illustrated by the regression test added in the previous commit. The underlying issue is that `effect.destroy` field is a mutable field but we read it during render. This is a concurrency bug — if we had a borrow checker, it would not allow this. It's rare in practice today because the field is updated during the commit phase, which takes a lock on the fiber tree until all the effects have fired. But it's still theoretically wrong because you can have multiple Fiber copies each with their own reference to a single destroy function, and indeed we discovered in production a scenario where this happens via our current APIs. In the future these types of scenarios will be much more common because we will introduce features where effects may run concurrently with the render phase — i.e. an imperative `hide` method that synchronously hides a React tree and unmounts all its effects without entering the render phase, and without interrupting a render phase that's already in progress. A future version of React may also be able to run the entire commit phase concurrently with a subsequent render phase. We can't do this now because our data structures are not fully thread safe (see: the Fiber alternate model) but we should be able to do this in the future. The fix I've introduced in this commit is to move the `destroy` field to a separate object. The effect "instance" is a shared object that remains the same for the entire lifetime of an effect. In Rust terms, a RefCell. The field is `undefined` if the effect is unmounted, or if the effect ran but is not stateful. We don't explicitly track whether the effect is mounted or unmounted because that can be inferred by the hiddenness of the fiber in the tree, i.e. whether there is a hidden Offscreen fiber above it. It's unfortunate that this is stored on a separate object, because it adds more memory per effect instance, but it's conceptually sound. I think there's likely a better data structure we could use for effects; perhaps just one array of effect instances per fiber. But I think this is OK for now despite the additional memory and we can follow up with performance optimizations later. --------- Co-authored-by: Dan Abramov <dan.abramov@gmail.com> Co-authored-by: Rick Hanlon <rickhanlonii@gmail.com> Co-authored-by: Jan Kassens <jan@kassens.net> DiffTrain build for [85bb7b6](85bb7b6)
Summary: This sync includes the following changes: - **[58742c21b](facebook/react@58742c21b )**: Delete unused `eventTimes` Fiber field ([#26599](facebook/react#26599)) //<Andrew Clark>// - **[0b931f90e](facebook/react@0b931f90e )**: Remove JND delay for non-transition updates ([#26597](facebook/react#26597)) //<Andrew Clark>// - **[ac43bf687](facebook/react@ac43bf687 )**: Move validation of text nesting into ReactDOMComponent ([#26594](facebook/react#26594)) //<Sebastian Markbåge>// - **[ca41adb8c](facebook/react@ca41adb8c )**: Diff properties in the commit phase instead of generating an update payload ([#26583](facebook/react#26583)) //<Sebastian Markbåge>// - **[dd0619b2e](facebook/react@dd0619b2e )**: rename $$$hostConfig to $$$config ([#26593](facebook/react#26593)) //<Josh Story>// - **[b55d31955](facebook/react@b55d31955 )**: Rename HostConfig files to FiberConfig to clarify they are configs fo… ([#26592](facebook/react#26592)) //<Josh Story>// - **[ffb8eaca5](facebook/react@ffb8eaca5 )**: Rename ReactServerFormatConfig to ReactFizzConfig ([#26591](facebook/react#26591)) //<Josh Story>// - **[f4f873f62](facebook/react@f4f873f62 )**: Implements wiring for Flight to have it's own "HostConfig" ([#26590](facebook/react#26590)) //<Josh Story>// - **[44db16afc](facebook/react@44db16afc )**: Normalize ReactFlightServerConfig and related files ([#26589](facebook/react#26589)) //<Josh Story>// - **[fec97ecbc](facebook/react@fec97ecbc )**: act: Move didScheduleLegacyUpdate to ensureRootIsScheduled ([#26552](facebook/react#26552)) //<Andrew Clark>// - **[9a9da7721](facebook/react@9a9da7721 )**: Don't update textarea defaultValue and input checked unnecessarily ([#26580](facebook/react#26580)) //<Sebastian Markbåge>// - **[e5146cb52](facebook/react@e5146cb52 )**: Refactor some controlled component stuff ([#26573](facebook/react#26573)) //<Sebastian Markbåge>// - **[657698e48](facebook/react@657698e48 )**: [Tests] `waitForThrow` should diff strings ([#26568](facebook/react#26568)) //<Josh Story>// - **[85bb7b685](facebook/react@85bb7b685 )**: Fix: Move `destroy` field to shared instance object ([#26561](facebook/react#26561)) //<Andrew Clark>// - **[9cfba0f6e](facebook/react@9cfba0f6e )**: Clean up discrete event replaying ([#26558](facebook/react#26558)) //<Sebastian Markbåge>// - **[790ebc962](facebook/react@790ebc962 )**: Remove no-fallthrough lint suppressions ([#26553](facebook/react#26553)) //<Sophie Alpert>// - **[c15579631](facebook/react@c15579631 )**: Put common aliases in Map/Set instead of switch over strings ([#26551](facebook/react#26551)) //<Sebastian Markbåge>// - **[d5fd60f7e](facebook/react@d5fd60f7e )**: Remove findInstanceBlockingEvent unused parameters ([#26534](facebook/react#26534)) //<Mohammad Ghorbani>// - **[eeabb7312](facebook/react@eeabb7312 )**: Refactor DOM Bindings Completely Off of DOMProperty Meta Programming ([#26546](facebook/react#26546)) //<Sebastian Markbåge>// - **[da94e8b24](facebook/react@da94e8b24 )**: Revert "Cleanup enableSyncDefaultUpdate flag ([#26236](facebook/react#26236))" ([#26528](facebook/react#26528)) //<Jan Kassens>// - **[0700dd50b](facebook/react@0700dd50b )**: Implement public instances for text nodes in Fabric ([#26516](facebook/react#26516)) //<Rubén Norte>// - **[4a1cc2ddd](facebook/react@4a1cc2ddd )**: Fix logic around attribute seralization ([#26526](facebook/react#26526)) //<Josh Story>// - **[7329ea81c](facebook/react@7329ea81c )**: Fix suspense replaying forward refs ([#26535](facebook/react#26535)) //<Hans Otto Wirtz>// - **[0ae348018](facebook/react@0ae348018 )**: [Float] Suspend unstyled content for up to 1 minute ([#26532](facebook/react#26532)) //<Andrew Clark>// - **[888874673](facebook/react@888874673 )**: Allow transitions to interrupt Suspensey commits ([#26531](facebook/react#26531)) //<Andrew Clark>// - **[09c8d2563](facebook/react@09c8d2563 )**: Move update scheduling to microtask ([#26512](facebook/react#26512)) //<Andrew Clark>// - **[8310854ce](facebook/react@8310854ce )**: Clean up enableCapturePhaseSelectiveHydrationWithoutDiscreteEventReplay ([#26521](facebook/react#26521)) //<Andrew Clark>// Changelog: [General][Changed] - React Native sync for revisions ca01f35...58742c2 jest_e2e[run_all_tests] bypass-github-export-checks Reviewed By: sammy-SC Differential Revision: D44872333 fbshipit-source-id: 0695e86645955aac7a20afdaf3ed02ad33592f5c
Summary: This sync includes the following changes: - **[58742c21b](facebook/react@58742c21b )**: Delete unused `eventTimes` Fiber field ([facebook#26599](facebook/react#26599)) //<Andrew Clark>// - **[0b931f90e](facebook/react@0b931f90e )**: Remove JND delay for non-transition updates ([facebook#26597](facebook/react#26597)) //<Andrew Clark>// - **[ac43bf687](facebook/react@ac43bf687 )**: Move validation of text nesting into ReactDOMComponent ([facebook#26594](facebook/react#26594)) //<Sebastian Markbåge>// - **[ca41adb8c](facebook/react@ca41adb8c )**: Diff properties in the commit phase instead of generating an update payload ([facebook#26583](facebook/react#26583)) //<Sebastian Markbåge>// - **[dd0619b2e](facebook/react@dd0619b2e )**: rename $$$hostConfig to $$$config ([facebook#26593](facebook/react#26593)) //<Josh Story>// - **[b55d31955](facebook/react@b55d31955 )**: Rename HostConfig files to FiberConfig to clarify they are configs fo… ([facebook#26592](facebook/react#26592)) //<Josh Story>// - **[ffb8eaca5](facebook/react@ffb8eaca5 )**: Rename ReactServerFormatConfig to ReactFizzConfig ([facebook#26591](facebook/react#26591)) //<Josh Story>// - **[f4f873f62](facebook/react@f4f873f62 )**: Implements wiring for Flight to have it's own "HostConfig" ([facebook#26590](facebook/react#26590)) //<Josh Story>// - **[44db16afc](facebook/react@44db16afc )**: Normalize ReactFlightServerConfig and related files ([facebook#26589](facebook/react#26589)) //<Josh Story>// - **[fec97ecbc](facebook/react@fec97ecbc )**: act: Move didScheduleLegacyUpdate to ensureRootIsScheduled ([facebook#26552](facebook/react#26552)) //<Andrew Clark>// - **[9a9da7721](facebook/react@9a9da7721 )**: Don't update textarea defaultValue and input checked unnecessarily ([facebook#26580](facebook/react#26580)) //<Sebastian Markbåge>// - **[e5146cb52](facebook/react@e5146cb52 )**: Refactor some controlled component stuff ([facebook#26573](facebook/react#26573)) //<Sebastian Markbåge>// - **[657698e48](facebook/react@657698e48 )**: [Tests] `waitForThrow` should diff strings ([facebook#26568](facebook/react#26568)) //<Josh Story>// - **[85bb7b685](facebook/react@85bb7b685 )**: Fix: Move `destroy` field to shared instance object ([facebook#26561](facebook/react#26561)) //<Andrew Clark>// - **[9cfba0f6e](facebook/react@9cfba0f6e )**: Clean up discrete event replaying ([facebook#26558](facebook/react#26558)) //<Sebastian Markbåge>// - **[790ebc962](facebook/react@790ebc962 )**: Remove no-fallthrough lint suppressions ([facebook#26553](facebook/react#26553)) //<Sophie Alpert>// - **[c15579631](facebook/react@c15579631 )**: Put common aliases in Map/Set instead of switch over strings ([facebook#26551](facebook/react#26551)) //<Sebastian Markbåge>// - **[d5fd60f7e](facebook/react@d5fd60f7e )**: Remove findInstanceBlockingEvent unused parameters ([facebook#26534](facebook/react#26534)) //<Mohammad Ghorbani>// - **[eeabb7312](facebook/react@eeabb7312 )**: Refactor DOM Bindings Completely Off of DOMProperty Meta Programming ([facebook#26546](facebook/react#26546)) //<Sebastian Markbåge>// - **[da94e8b24](facebook/react@da94e8b24 )**: Revert "Cleanup enableSyncDefaultUpdate flag ([facebook#26236](facebook/react#26236))" ([facebook#26528](facebook/react#26528)) //<Jan Kassens>// - **[0700dd50b](facebook/react@0700dd50b )**: Implement public instances for text nodes in Fabric ([facebook#26516](facebook/react#26516)) //<Rubén Norte>// - **[4a1cc2ddd](facebook/react@4a1cc2ddd )**: Fix logic around attribute seralization ([facebook#26526](facebook/react#26526)) //<Josh Story>// - **[7329ea81c](facebook/react@7329ea81c )**: Fix suspense replaying forward refs ([facebook#26535](facebook/react#26535)) //<Hans Otto Wirtz>// - **[0ae348018](facebook/react@0ae348018 )**: [Float] Suspend unstyled content for up to 1 minute ([facebook#26532](facebook/react#26532)) //<Andrew Clark>// - **[888874673](facebook/react@888874673 )**: Allow transitions to interrupt Suspensey commits ([facebook#26531](facebook/react#26531)) //<Andrew Clark>// - **[09c8d2563](facebook/react@09c8d2563 )**: Move update scheduling to microtask ([facebook#26512](facebook/react#26512)) //<Andrew Clark>// - **[8310854ce](facebook/react@8310854ce )**: Clean up enableCapturePhaseSelectiveHydrationWithoutDiscreteEventReplay ([facebook#26521](facebook/react#26521)) //<Andrew Clark>// Changelog: [General][Changed] - React Native sync for revisions ca01f35...58742c2 jest_e2e[run_all_tests] bypass-github-export-checks Reviewed By: sammy-SC Differential Revision: D44872333 fbshipit-source-id: 0695e86645955aac7a20afdaf3ed02ad33592f5c
Summary: This sync includes the following changes: - **[58742c21b](facebook/react@58742c21b )**: Delete unused `eventTimes` Fiber field ([facebook#26599](facebook/react#26599)) //<Andrew Clark>// - **[0b931f90e](facebook/react@0b931f90e )**: Remove JND delay for non-transition updates ([facebook#26597](facebook/react#26597)) //<Andrew Clark>// - **[ac43bf687](facebook/react@ac43bf687 )**: Move validation of text nesting into ReactDOMComponent ([facebook#26594](facebook/react#26594)) //<Sebastian Markbåge>// - **[ca41adb8c](facebook/react@ca41adb8c )**: Diff properties in the commit phase instead of generating an update payload ([facebook#26583](facebook/react#26583)) //<Sebastian Markbåge>// - **[dd0619b2e](facebook/react@dd0619b2e )**: rename $$$hostConfig to $$$config ([facebook#26593](facebook/react#26593)) //<Josh Story>// - **[b55d31955](facebook/react@b55d31955 )**: Rename HostConfig files to FiberConfig to clarify they are configs fo… ([facebook#26592](facebook/react#26592)) //<Josh Story>// - **[ffb8eaca5](facebook/react@ffb8eaca5 )**: Rename ReactServerFormatConfig to ReactFizzConfig ([facebook#26591](facebook/react#26591)) //<Josh Story>// - **[f4f873f62](facebook/react@f4f873f62 )**: Implements wiring for Flight to have it's own "HostConfig" ([facebook#26590](facebook/react#26590)) //<Josh Story>// - **[44db16afc](facebook/react@44db16afc )**: Normalize ReactFlightServerConfig and related files ([facebook#26589](facebook/react#26589)) //<Josh Story>// - **[fec97ecbc](facebook/react@fec97ecbc )**: act: Move didScheduleLegacyUpdate to ensureRootIsScheduled ([facebook#26552](facebook/react#26552)) //<Andrew Clark>// - **[9a9da7721](facebook/react@9a9da7721 )**: Don't update textarea defaultValue and input checked unnecessarily ([facebook#26580](facebook/react#26580)) //<Sebastian Markbåge>// - **[e5146cb52](facebook/react@e5146cb52 )**: Refactor some controlled component stuff ([facebook#26573](facebook/react#26573)) //<Sebastian Markbåge>// - **[657698e48](facebook/react@657698e48 )**: [Tests] `waitForThrow` should diff strings ([facebook#26568](facebook/react#26568)) //<Josh Story>// - **[85bb7b685](facebook/react@85bb7b685 )**: Fix: Move `destroy` field to shared instance object ([facebook#26561](facebook/react#26561)) //<Andrew Clark>// - **[9cfba0f6e](facebook/react@9cfba0f6e )**: Clean up discrete event replaying ([facebook#26558](facebook/react#26558)) //<Sebastian Markbåge>// - **[790ebc962](facebook/react@790ebc962 )**: Remove no-fallthrough lint suppressions ([facebook#26553](facebook/react#26553)) //<Sophie Alpert>// - **[c15579631](facebook/react@c15579631 )**: Put common aliases in Map/Set instead of switch over strings ([facebook#26551](facebook/react#26551)) //<Sebastian Markbåge>// - **[d5fd60f7e](facebook/react@d5fd60f7e )**: Remove findInstanceBlockingEvent unused parameters ([facebook#26534](facebook/react#26534)) //<Mohammad Ghorbani>// - **[eeabb7312](facebook/react@eeabb7312 )**: Refactor DOM Bindings Completely Off of DOMProperty Meta Programming ([facebook#26546](facebook/react#26546)) //<Sebastian Markbåge>// - **[da94e8b24](facebook/react@da94e8b24 )**: Revert "Cleanup enableSyncDefaultUpdate flag ([facebook#26236](facebook/react#26236))" ([facebook#26528](facebook/react#26528)) //<Jan Kassens>// - **[0700dd50b](facebook/react@0700dd50b )**: Implement public instances for text nodes in Fabric ([facebook#26516](facebook/react#26516)) //<Rubén Norte>// - **[4a1cc2ddd](facebook/react@4a1cc2ddd )**: Fix logic around attribute seralization ([facebook#26526](facebook/react#26526)) //<Josh Story>// - **[7329ea81c](facebook/react@7329ea81c )**: Fix suspense replaying forward refs ([facebook#26535](facebook/react#26535)) //<Hans Otto Wirtz>// - **[0ae348018](facebook/react@0ae348018 )**: [Float] Suspend unstyled content for up to 1 minute ([facebook#26532](facebook/react#26532)) //<Andrew Clark>// - **[888874673](facebook/react@888874673 )**: Allow transitions to interrupt Suspensey commits ([facebook#26531](facebook/react#26531)) //<Andrew Clark>// - **[09c8d2563](facebook/react@09c8d2563 )**: Move update scheduling to microtask ([facebook#26512](facebook/react#26512)) //<Andrew Clark>// - **[8310854ce](facebook/react@8310854ce )**: Clean up enableCapturePhaseSelectiveHydrationWithoutDiscreteEventReplay ([facebook#26521](facebook/react#26521)) //<Andrew Clark>// Changelog: [General][Changed] - React Native sync for revisions ca01f35...58742c2 jest_e2e[run_all_tests] bypass-github-export-checks Reviewed By: sammy-SC Differential Revision: D44872333 fbshipit-source-id: 0695e86645955aac7a20afdaf3ed02ad33592f5c
This fixes the "double free" bug illustrated by the regression test added in the previous commit. The underlying issue is that `effect.destroy` field is a mutable field but we read it during render. This is a concurrency bug — if we had a borrow checker, it would not allow this. It's rare in practice today because the field is updated during the commit phase, which takes a lock on the fiber tree until all the effects have fired. But it's still theoretically wrong because you can have multiple Fiber copies each with their own reference to a single destroy function, and indeed we discovered in production a scenario where this happens via our current APIs. In the future these types of scenarios will be much more common because we will introduce features where effects may run concurrently with the render phase — i.e. an imperative `hide` method that synchronously hides a React tree and unmounts all its effects without entering the render phase, and without interrupting a render phase that's already in progress. A future version of React may also be able to run the entire commit phase concurrently with a subsequent render phase. We can't do this now because our data structures are not fully thread safe (see: the Fiber alternate model) but we should be able to do this in the future. The fix I've introduced in this commit is to move the `destroy` field to a separate object. The effect "instance" is a shared object that remains the same for the entire lifetime of an effect. In Rust terms, a RefCell. The field is `undefined` if the effect is unmounted, or if the effect ran but is not stateful. We don't explicitly track whether the effect is mounted or unmounted because that can be inferred by the hiddenness of the fiber in the tree, i.e. whether there is a hidden Offscreen fiber above it. It's unfortunate that this is stored on a separate object, because it adds more memory per effect instance, but it's conceptually sound. I think there's likely a better data structure we could use for effects; perhaps just one array of effect instances per fiber. But I think this is OK for now despite the additional memory and we can follow up with performance optimizations later. --------- Co-authored-by: Dan Abramov <dan.abramov@gmail.com> Co-authored-by: Rick Hanlon <rickhanlonii@gmail.com> Co-authored-by: Jan Kassens <jan@kassens.net>
This fixes the "double free" bug illustrated by the regression test added in the previous commit. The underlying issue is that `effect.destroy` field is a mutable field but we read it during render. This is a concurrency bug — if we had a borrow checker, it would not allow this. It's rare in practice today because the field is updated during the commit phase, which takes a lock on the fiber tree until all the effects have fired. But it's still theoretically wrong because you can have multiple Fiber copies each with their own reference to a single destroy function, and indeed we discovered in production a scenario where this happens via our current APIs. In the future these types of scenarios will be much more common because we will introduce features where effects may run concurrently with the render phase — i.e. an imperative `hide` method that synchronously hides a React tree and unmounts all its effects without entering the render phase, and without interrupting a render phase that's already in progress. A future version of React may also be able to run the entire commit phase concurrently with a subsequent render phase. We can't do this now because our data structures are not fully thread safe (see: the Fiber alternate model) but we should be able to do this in the future. The fix I've introduced in this commit is to move the `destroy` field to a separate object. The effect "instance" is a shared object that remains the same for the entire lifetime of an effect. In Rust terms, a RefCell. The field is `undefined` if the effect is unmounted, or if the effect ran but is not stateful. We don't explicitly track whether the effect is mounted or unmounted because that can be inferred by the hiddenness of the fiber in the tree, i.e. whether there is a hidden Offscreen fiber above it. It's unfortunate that this is stored on a separate object, because it adds more memory per effect instance, but it's conceptually sound. I think there's likely a better data structure we could use for effects; perhaps just one array of effect instances per fiber. But I think this is OK for now despite the additional memory and we can follow up with performance optimizations later. --------- Co-authored-by: Dan Abramov <dan.abramov@gmail.com> Co-authored-by: Rick Hanlon <rickhanlonii@gmail.com> Co-authored-by: Jan Kassens <jan@kassens.net> DiffTrain build for commit 85bb7b6.
This fixes the "double free" bug illustrated by the regression test added in the previous commit.
The underlying issue is that
effect.destroy
field is a mutable field but we read it during render. This is a concurrency bug — if we had a borrow checker, it would not allow this.It's rare in practice today because the field is updated during the commit phase, which takes a lock on the fiber tree until all the effects have fired. But it's still theoretically wrong because you can have multiple Fiber copies each with their own reference to a single destroy function, and indeed we discovered in production a scenario where this happens via our current APIs.
In the future these types of scenarios will be much more common because we will introduce features where effects may run concurrently with the render phase — i.e. an imperative
hide
method that synchronously hides a React tree and unmounts all its effects without entering the render phase, and without interrupting a render phase that's already in progress.A future version of React may also be able to run the entire commit phase concurrently with a subsequent render phase. We can't do this now because our data structures are not fully thread safe (see: the Fiber alternate model) but we should be able to do this in the future.
The fix I've introduced in this commit is to move the
destroy
field to a separate object. The effect "instance" is a shared object that remains the same for the entire lifetime of an effect. In Rust terms, a RefCell. The field isundefined
if the effect is unmounted, or if the effect ran but is not stateful. We don't explicitly track whether the effect is mounted or unmounted because that can be inferred by the hiddenness of the fiber in the tree, i.e. whether there is a hidden Offscreen fiber above it.It's unfortunate that this is stored on a separate object, because it adds more memory per effect instance, but it's conceptually sound. I think there's likely a better data structure we could use for effects; perhaps just one array of effect instances per fiber. But I think this is OK for now despite the additional memory and we can follow up with performance optimizations later.