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

[Synchronous Suspense] Reuse deletions from primary tree #14133

Merged
merged 1 commit into from
Nov 7, 2018

Conversation

acdlite
Copy link
Collaborator

@acdlite acdlite commented Nov 7, 2018

Fixes a bug where deletion effects in the primary tree were dropped before entering the second render pass.

Because we no longer reset the effect list after the first render pass, I've also moved the deletion of the fallback children to the complete phase, after the tree successfully renders without suspending.

Will need to revisit this heuristic when we implement resuming.

@sizebot
Copy link

sizebot commented Nov 7, 2018

ReactDOM: size: -0.4%, gzip: -0.3%

Details of bundled changes.

Comparing: aa1ffe4...7788bee

react-dom

File Filesize Diff Gzip Diff Prev Size Current Size Prev Gzip Current Gzip ENV
react-dom.development.js -0.2% -0.1% 708.51 KB 707.22 KB 163.81 KB 163.62 KB UMD_DEV
react-dom.production.min.js -0.4% -0.3% 97.96 KB 97.56 KB 31.85 KB 31.76 KB UMD_PROD
react-dom.development.js -0.2% -0.1% 703.82 KB 702.53 KB 162.43 KB 162.25 KB NODE_DEV
react-dom.production.min.js -0.4% -0.3% 97.95 KB 97.56 KB 31.39 KB 31.3 KB NODE_PROD
ReactDOM-dev.js -0.2% -0.1% 724.71 KB 723.26 KB 163.62 KB 163.39 KB FB_WWW_DEV
ReactDOM-prod.js -0.4% -0.3% 310.9 KB 309.56 KB 57.19 KB 57.02 KB FB_WWW_PROD
react-dom.profiling.min.js -0.4% -0.3% 100.72 KB 100.33 KB 31.92 KB 31.83 KB NODE_PROFILING
ReactDOM-profiling.js -0.4% -0.3% 317.1 KB 315.76 KB 58.44 KB 58.25 KB FB_WWW_PROFILING
react-dom.profiling.min.js -0.4% -0.3% 100.63 KB 100.24 KB 32.48 KB 32.38 KB UMD_PROFILING

react-art

File Filesize Diff Gzip Diff Prev Size Current Size Prev Gzip Current Gzip ENV
react-art.development.js -0.3% -0.2% 495.65 KB 494.36 KB 109.44 KB 109.26 KB UMD_DEV
react-art.production.min.js -0.4% -0.4% 90.12 KB 89.73 KB 27.67 KB 27.56 KB UMD_PROD
react-art.development.js -0.3% -0.2% 427.43 KB 426.14 KB 92.39 KB 92.23 KB NODE_DEV
react-art.production.min.js -0.7% -0.6% 55.11 KB 54.72 KB 16.99 KB 16.89 KB NODE_PROD
ReactART-dev.js -0.3% -0.3% 434.73 KB 433.28 KB 91.37 KB 91.12 KB FB_WWW_DEV
ReactART-prod.js -0.7% -0.6% 184.7 KB 183.48 KB 31.51 KB 31.33 KB FB_WWW_PROD

react-test-renderer

File Filesize Diff Gzip Diff Prev Size Current Size Prev Gzip Current Gzip ENV
react-test-renderer.development.js -0.3% -0.2% 440.09 KB 438.8 KB 95.1 KB 94.92 KB UMD_DEV
react-test-renderer.production.min.js -0.7% -0.5% 56.32 KB 55.94 KB 17.28 KB 17.2 KB UMD_PROD
react-test-renderer.development.js -0.3% -0.2% 435.31 KB 434.02 KB 93.95 KB 93.77 KB NODE_DEV
react-test-renderer.production.min.js -0.7% -0.4% 56 KB 55.61 KB 17.13 KB 17.06 KB NODE_PROD
ReactTestRenderer-dev.js -0.3% -0.3% 442.79 KB 441.35 KB 93.21 KB 92.97 KB FB_WWW_DEV

react-reconciler

File Filesize Diff Gzip Diff Prev Size Current Size Prev Gzip Current Gzip ENV
react-reconciler.development.js -0.3% -0.2% 425.27 KB 423.98 KB 90.89 KB 90.7 KB NODE_DEV
react-reconciler.production.min.js -0.7% -0.5% 56.24 KB 55.85 KB 16.84 KB 16.75 KB NODE_PROD
react-reconciler-persistent.development.js -0.3% -0.2% 423.72 KB 422.43 KB 90.26 KB 90.08 KB NODE_DEV
react-reconciler-persistent.production.min.js -0.7% -0.5% 56.25 KB 55.86 KB 16.84 KB 16.76 KB NODE_PROD

react-native-renderer

File Filesize Diff Gzip Diff Prev Size Current Size Prev Gzip Current Gzip ENV
ReactNativeRenderer-dev.js -0.3% -0.2% 559.92 KB 558.47 KB 122.03 KB 121.8 KB RN_FB_DEV
ReactNativeRenderer-prod.js -0.6% -0.5% 240.22 KB 238.8 KB 42.21 KB 42.01 KB RN_FB_PROD
ReactNativeRenderer-dev.js -0.3% -0.2% 559.62 KB 558.17 KB 121.94 KB 121.7 KB RN_OSS_DEV
ReactNativeRenderer-prod.js -0.6% -0.5% 225.66 KB 224.24 KB 39.14 KB 38.94 KB RN_OSS_PROD
ReactFabric-dev.js -0.3% -0.2% 550.23 KB 548.79 KB 119.55 KB 119.3 KB RN_FB_DEV
ReactFabric-prod.js -0.7% -0.5% 219.91 KB 218.45 KB 37.82 KB 37.63 KB RN_FB_PROD
ReactFabric-dev.js -0.3% -0.2% 550.27 KB 548.82 KB 119.56 KB 119.32 KB RN_OSS_DEV
ReactFabric-prod.js -0.7% -0.5% 219.95 KB 218.49 KB 37.84 KB 37.64 KB RN_OSS_PROD
ReactNativeRenderer-profiling.js -0.6% -0.5% 230.85 KB 229.4 KB 40.33 KB 40.13 KB RN_OSS_PROFILING
ReactFabric-profiling.js -0.7% -0.5% 224.21 KB 222.75 KB 39.07 KB 38.88 KB RN_OSS_PROFILING
ReactNativeRenderer-profiling.js -0.6% -0.5% 245.66 KB 244.21 KB 43.41 KB 43.21 KB RN_FB_PROFILING
ReactFabric-profiling.js -0.7% -0.5% 224.17 KB 222.71 KB 39.05 KB 38.86 KB RN_FB_PROFILING

scheduler

File Filesize Diff Gzip Diff Prev Size Current Size Prev Gzip Current Gzip ENV
scheduler.development.js n/a n/a 0 B 19.17 KB 0 B 5.74 KB UMD_DEV
scheduler.production.min.js n/a n/a 0 B 3.16 KB 0 B 1.53 KB UMD_PROD

Generated by 🚫 dangerJS

@@ -993,7 +993,6 @@ function completeUnitOfWork(workInProgress: Fiber): Fiber | null {

if (nextUnitOfWork !== null) {
// Completing this fiber spawned new work. Work on that next.
nextUnitOfWork.firstEffect = nextUnitOfWork.lastEffect = null;
Copy link
Collaborator

Choose a reason for hiding this comment

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

So this was the thing that caused us to "forget" about deletions?

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

Yes

// We just switched from the fallback to the normal children. Delete
// the fallback.
// TODO: Would it be better to store the fallback fragment on
// the stateNode during the begin phase?
Copy link
Collaborator

Choose a reason for hiding this comment

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

Why would it be better?

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

To avoid the confusing series of checks this comment is wrapped in. Could be replaced by checking if the stateNode is null.

I'm not sure that's actually better though. Kinda gross either way.

// the stateNode during the begin phase?
const currentFallbackChild: Fiber | null = (current.child: any).sibling;
if (currentFallbackChild !== null) {
reconcileChildFibers(
Copy link
Collaborator

Choose a reason for hiding this comment

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

Would it be simpler if instead of deleting the fallback we kept it hidden? Although that seems bad from memory perspective.

Copy link
Collaborator Author

@acdlite acdlite Nov 7, 2018

Choose a reason for hiding this comment

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

It would be simpler because we would always render two fragments, instead of switching between fragments and no fragments like we do now. That's why updateSuspenseComponent has so much code in it; we don't do this type of custom reconciliation anywhere else, yet. Combined with the other weird things we do with Suspense (committing incomplete trees in sync mode, hiding instead of deleting after a timeout) it's gotten pretty complex. Trade off still seems worth it, but unfortunately that means it might take longer to flush out all these edge cases.

(I keep trying to think of a good fuzz test, but I can't. Haven't given up though.)

Copy link
Collaborator

@gaearon gaearon left a comment

Choose a reason for hiding this comment

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

If I understand this PR correctly it just lets the suspense follow the normal flow of how effects are marked instead of trying to clear/reuse them separately, and that lets us not lose deletions. I think this makes sense to me.

@acdlite
Copy link
Collaborator Author

acdlite commented Nov 7, 2018

Going to merge so we can try this out on www. @sebmarkbage let me know if this fix doesn't match what we verbally agreed to yesterday.

Fixes a bug where deletion effects in the primary tree were dropped
before entering the second render pass.

Because we no longer reset the effect list after the first render pass,
I've also moved the deletion of the fallback children to the complete
phase, after the tree successfully renders without suspending.

Will need to revisit this heuristic when we implement resuming.
@acdlite acdlite merged commit e27720d into facebook:master Nov 7, 2018
@sebmarkbage
Copy link
Collaborator

This makes sense to me but is also scary. :/

acdlite added a commit to acdlite/react that referenced this pull request Nov 8, 2018
The fuzzer works by generating a random tree of React elements. The
tree two types of custom components:

- A Text component suspends rendering on initial mount for a fuzzy
  duration of time. It may update a fuzzy number of times; each update
  supsends for a fuzzy duration of time.
- A Container component wraps some children. It may remount its children
  a fuzzy number of times, by updating its key.

The tree may also include nested Suspense components.

After this tree is generated, the tester sets a flag to temporarily
disable Text components from suspending. The tree is rendered
synchronously. The output of this render is the expected output.

Then the tester flips the flag back to enable suspending. It renders
the tree again. This time the Text components will suspend for the amount of time
configured by the props. The tester waits until everything has resolved. The resolved
output is then compared to the expected output generated in the previous step.

Finally, we render once more, but this time in concurrent mode. Once again, the resolved output is generated
to the expected output.

I tested by commenting out various parts of the Suspense implementation
to see if broke in the expected way. I also confirmed that it would have
caught facebook#14133, a recent bug related to deletions.
acdlite added a commit to acdlite/react that referenced this pull request Nov 8, 2018
The fuzzer works by generating a random tree of React elements. The
tree two types of custom components:

- A Text component suspends rendering on initial mount for a fuzzy
  duration of time. It may update a fuzzy number of times; each update
  supsends for a fuzzy duration of time.
- A Container component wraps some children. It may remount its children
  a fuzzy number of times, by updating its key.

The tree may also include nested Suspense components.

After this tree is generated, the tester sets a flag to temporarily
disable Text components from suspending. The tree is rendered
synchronously. The output of this render is the expected output.

Then the tester flips the flag back to enable suspending. It renders
the tree again. This time the Text components will suspend for the amount of time
configured by the props. The tester waits until everything has resolved. The resolved
output is then compared to the expected output generated in the previous step.

Finally, we render once more, but this time in concurrent mode. Once again, the resolved output is generated
to the expected output.

I tested by commenting out various parts of the Suspense implementation
to see if broke in the expected way. I also confirmed that it would have
caught facebook#14133, a recent bug related to deletions.
@acdlite acdlite mentioned this pull request Nov 8, 2018
acdlite added a commit to acdlite/react that referenced this pull request Nov 8, 2018
The fuzzer works by generating a random tree of React elements. The tree
two types of custom components:

- A Text component suspends rendering on initial mount for a fuzzy
  duration of time. It may update a fuzzy number of times; each update
  supsends for a fuzzy duration of time.
- A Container component wraps some children. It may remount its children
  a fuzzy number of times, by updating its key.

The tree may also include nested Suspense components.

After this tree is generated, the tester sets a flag to temporarily
disable Text components from suspending. The tree is rendered
synchronously. The output of this render is the expected output.

Then the tester flips the flag back to enable suspending. It renders the
tree again. This time the Text components will suspend for the amount of
time configured by the props. The tester waits until everything has
resolved. The resolved output is then compared to the expected output
generated in the previous step.

Finally, we render once more, but this time in concurrent mode. Once
again, the resolved output is compared to the expected output.

I tested by commenting out various parts of the Suspense implementation
to see if broke in the expected way. I also confirmed that it would have
caught facebook#14133, a recent bug related to deletions.
acdlite added a commit to acdlite/react that referenced this pull request Nov 8, 2018
The fuzzer works by generating a random tree of React elements. The tree
two types of custom components:

- A Text component suspends rendering on initial mount for a fuzzy
  duration of time. It may update a fuzzy number of times; each update
  supsends for a fuzzy duration of time.
- A Container component wraps some children. It may remount its children
  a fuzzy number of times, by updating its key.

The tree may also include nested Suspense components.

After this tree is generated, the tester sets a flag to temporarily
disable Text components from suspending. The tree is rendered
synchronously. The output of this render is the expected output.

Then the tester flips the flag back to enable suspending. It renders the
tree again. This time the Text components will suspend for the amount of
time configured by the props. The tester waits until everything has
resolved. The resolved output is then compared to the expected output
generated in the previous step.

Finally, we render once more, but this time in concurrent mode. Once
again, the resolved output is compared to the expected output.

I tested by commenting out various parts of the Suspense implementation
to see if broke in the expected way. I also confirmed that it would have
caught facebook#14133, a recent bug related to deletions.
acdlite added a commit to acdlite/react that referenced this pull request Nov 8, 2018
The fuzzer works by generating a random tree of React elements. The tree
two types of custom components:

- A Text component suspends rendering on initial mount for a fuzzy
  duration of time. It may update a fuzzy number of times; each update
  supsends for a fuzzy duration of time.
- A Container component wraps some children. It may remount its children
  a fuzzy number of times, by updating its key.

The tree may also include nested Suspense components.

After this tree is generated, the tester sets a flag to temporarily
disable Text components from suspending. The tree is rendered
synchronously. The output of this render is the expected output.

Then the tester flips the flag back to enable suspending. It renders the
tree again. This time the Text components will suspend for the amount of
time configured by the props. The tester waits until everything has
resolved. The resolved output is then compared to the expected output
generated in the previous step.

Finally, we render once more, but this time in concurrent mode. Once
again, the resolved output is compared to the expected output.

I tested by commenting out various parts of the Suspense implementation
to see if broke in the expected way. I also confirmed that it would have
caught facebook#14133, a recent bug related to deletions.
acdlite added a commit to acdlite/react that referenced this pull request Nov 8, 2018
The fuzzer works by generating a random tree of React elements. The tree
two types of custom components:

- A Text component suspends rendering on initial mount for a fuzzy
  duration of time. It may update a fuzzy number of times; each update
  supsends for a fuzzy duration of time.
- A Container component wraps some children. It may remount its children
  a fuzzy number of times, by updating its key.

The tree may also include nested Suspense components.

After this tree is generated, the tester sets a flag to temporarily
disable Text components from suspending. The tree is rendered
synchronously. The output of this render is the expected output.

Then the tester flips the flag back to enable suspending. It renders the
tree again. This time the Text components will suspend for the amount of
time configured by the props. The tester waits until everything has
resolved. The resolved output is then compared to the expected output
generated in the previous step.

Finally, we render once more, but this time in concurrent mode. Once
again, the resolved output is compared to the expected output.

I tested by commenting out various parts of the Suspense implementation
to see if broke in the expected way. I also confirmed that it would have
caught facebook#14133, a recent bug related to deletions.
acdlite added a commit to acdlite/react that referenced this pull request Nov 8, 2018
The fuzzer works by generating a random tree of React elements. The tree
two types of custom components:

- A Text component suspends rendering on initial mount for a fuzzy
  duration of time. It may update a fuzzy number of times; each update
  supsends for a fuzzy duration of time.
- A Container component wraps some children. It may remount its children
  a fuzzy number of times, by updating its key.

The tree may also include nested Suspense components.

After this tree is generated, the tester sets a flag to temporarily
disable Text components from suspending. The tree is rendered
synchronously. The output of this render is the expected output.

Then the tester flips the flag back to enable suspending. It renders the
tree again. This time the Text components will suspend for the amount of
time configured by the props. The tester waits until everything has
resolved. The resolved output is then compared to the expected output
generated in the previous step.

Finally, we render once more, but this time in concurrent mode. Once
again, the resolved output is compared to the expected output.

I tested by commenting out various parts of the Suspense implementation
to see if broke in the expected way. I also confirmed that it would have
caught facebook#14133, a recent bug related to deletions.
acdlite added a commit that referenced this pull request Nov 9, 2018
* Don't warn if an unmounted component is pinged

* Suspense fuzz tester

The fuzzer works by generating a random tree of React elements. The tree
two types of custom components:

- A Text component suspends rendering on initial mount for a fuzzy
  duration of time. It may update a fuzzy number of times; each update
  supsends for a fuzzy duration of time.
- A Container component wraps some children. It may remount its children
  a fuzzy number of times, by updating its key.

The tree may also include nested Suspense components.

After this tree is generated, the tester sets a flag to temporarily
disable Text components from suspending. The tree is rendered
synchronously. The output of this render is the expected output.

Then the tester flips the flag back to enable suspending. It renders the
tree again. This time the Text components will suspend for the amount of
time configured by the props. The tester waits until everything has
resolved. The resolved output is then compared to the expected output
generated in the previous step.

Finally, we render once more, but this time in concurrent mode. Once
again, the resolved output is compared to the expected output.

I tested by commenting out various parts of the Suspense implementation
to see if broke in the expected way. I also confirmed that it would have
caught #14133, a recent bug related to deletions.

* When a generated test case fails, log its input

* Moar fuzziness

Adds more fuzziness to the generated tests. Specifcally, introduces
nested Suspense cases, where the fallback of a Suspense component
also suspends.

This flushed out a bug (yay!) whose test case I've hard coded.

* Use seeded random number generator

So if there's a failure, we can bisect.
@gaearon gaearon mentioned this pull request Nov 13, 2018
jetoneza pushed a commit to jetoneza/react that referenced this pull request Jan 23, 2019
)

Fixes a bug where deletion effects in the primary tree were dropped
before entering the second render pass.

Because we no longer reset the effect list after the first render pass,
I've also moved the deletion of the fallback children to the complete
phase, after the tree successfully renders without suspending.

Will need to revisit this heuristic when we implement resuming.
jetoneza pushed a commit to jetoneza/react that referenced this pull request Jan 23, 2019
* Don't warn if an unmounted component is pinged

* Suspense fuzz tester

The fuzzer works by generating a random tree of React elements. The tree
two types of custom components:

- A Text component suspends rendering on initial mount for a fuzzy
  duration of time. It may update a fuzzy number of times; each update
  supsends for a fuzzy duration of time.
- A Container component wraps some children. It may remount its children
  a fuzzy number of times, by updating its key.

The tree may also include nested Suspense components.

After this tree is generated, the tester sets a flag to temporarily
disable Text components from suspending. The tree is rendered
synchronously. The output of this render is the expected output.

Then the tester flips the flag back to enable suspending. It renders the
tree again. This time the Text components will suspend for the amount of
time configured by the props. The tester waits until everything has
resolved. The resolved output is then compared to the expected output
generated in the previous step.

Finally, we render once more, but this time in concurrent mode. Once
again, the resolved output is compared to the expected output.

I tested by commenting out various parts of the Suspense implementation
to see if broke in the expected way. I also confirmed that it would have
caught facebook#14133, a recent bug related to deletions.

* When a generated test case fails, log its input

* Moar fuzziness

Adds more fuzziness to the generated tests. Specifcally, introduces
nested Suspense cases, where the fallback of a Suspense component
also suspends.

This flushed out a bug (yay!) whose test case I've hard coded.

* Use seeded random number generator

So if there's a failure, we can bisect.
n8schloss pushed a commit to n8schloss/react that referenced this pull request Jan 31, 2019
)

Fixes a bug where deletion effects in the primary tree were dropped
before entering the second render pass.

Because we no longer reset the effect list after the first render pass,
I've also moved the deletion of the fallback children to the complete
phase, after the tree successfully renders without suspending.

Will need to revisit this heuristic when we implement resuming.
n8schloss pushed a commit to n8schloss/react that referenced this pull request Jan 31, 2019
* Don't warn if an unmounted component is pinged

* Suspense fuzz tester

The fuzzer works by generating a random tree of React elements. The tree
two types of custom components:

- A Text component suspends rendering on initial mount for a fuzzy
  duration of time. It may update a fuzzy number of times; each update
  supsends for a fuzzy duration of time.
- A Container component wraps some children. It may remount its children
  a fuzzy number of times, by updating its key.

The tree may also include nested Suspense components.

After this tree is generated, the tester sets a flag to temporarily
disable Text components from suspending. The tree is rendered
synchronously. The output of this render is the expected output.

Then the tester flips the flag back to enable suspending. It renders the
tree again. This time the Text components will suspend for the amount of
time configured by the props. The tester waits until everything has
resolved. The resolved output is then compared to the expected output
generated in the previous step.

Finally, we render once more, but this time in concurrent mode. Once
again, the resolved output is compared to the expected output.

I tested by commenting out various parts of the Suspense implementation
to see if broke in the expected way. I also confirmed that it would have
caught facebook#14133, a recent bug related to deletions.

* When a generated test case fails, log its input

* Moar fuzziness

Adds more fuzziness to the generated tests. Specifcally, introduces
nested Suspense cases, where the fallback of a Suspense component
also suspends.

This flushed out a bug (yay!) whose test case I've hard coded.

* Use seeded random number generator

So if there's a failure, we can bisect.
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.

5 participants