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

Reconciler: Don't retry synchronous render #28810

Draft
wants to merge 4 commits into
base: main
Choose a base branch
from

Conversation

eps1lon
Copy link
Collaborator

@eps1lon eps1lon commented Apr 10, 2024

Summary

In #13041 we started retrying the render synchronously if a render threw during a concurrent render. However, this also applied to synchronous renders which should help very rarely since we don't support mutations during render. The original PR even explicitly said that a synchronous render should not be retried. We also didn't used to retry in legacy roots: https://react-error-recovery-showcase.vercel.app/

A lot of tests asserted on the retry even though their render was sync i.e. not wrapped in startTransition.

I originally caught this while investigating duplicate host instance creation in error boundary fallbacks. But this is expected by proxy of having error recovery for sync renders. Host instances are created during render phase.

How did you test this change?

@facebook-github-bot facebook-github-bot added CLA Signed React Core Team Opened by a member of the React Core Team labels Apr 10, 2024
@@ -1066,10 +1066,10 @@ describe(`onRender`, () => {

// start time: 5 initially + 14 of work
// Add an additional 3 (ThrowsError) if we replayed the failed work
expect(mountCall[4]).toBe(19);
expect(mountCall[4]).toBe(5);
Copy link
Collaborator Author

Choose a reason for hiding this comment

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

TODO: Update comments

@react-sizebot
Copy link

react-sizebot commented Apr 10, 2024

Comparing: 5b903cd...00d183b

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.js +0.04% 497.54 kB 497.71 kB +0.05% 88.93 kB 88.98 kB
oss-experimental/react-dom/cjs/react-dom.production.js +0.03% 502.86 kB 503.04 kB +0.05% 89.84 kB 89.88 kB
facebook-www/ReactDOM-prod.classic.js +0.03% 591.00 kB 591.18 kB +0.04% 103.95 kB 103.99 kB
facebook-www/ReactDOM-prod.modern.js +0.03% 566.82 kB 566.99 kB +0.06% 100.15 kB 100.21 kB
test_utils/ReactAllWarnings.js Deleted 64.68 kB 0.00 kB Deleted 16.15 kB 0.00 kB

Significant size changes

Includes any change greater than 0.2%:

Expand to show
Name +/- Base Current +/- gzip Base gzip Current gzip
test_utils/ReactAllWarnings.js Deleted 64.68 kB 0.00 kB Deleted 16.15 kB 0.00 kB

Generated by 🚫 dangerJS against 00d183b

@eps1lon eps1lon force-pushed the error-boundary-double-commit-26518 branch 2 times, most recently from f5663e5 to ced79ba Compare April 11, 2024 07:45
// Check if something threw
if (exitStatus === RootErrored) {
// Check if something threw.
// We only need to retry during hydration or concurrent render.
Copy link
Collaborator

Choose a reason for hiding this comment

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

We shouldn't even do it for those because we should refactor the Root recovery to work the same way as Suspense recovery.

@eps1lon eps1lon force-pushed the error-boundary-double-commit-26518 branch from ced79ba to cd59515 Compare April 21, 2024 15:27
Comment on lines 1808 to 1908
await act(async () => {
// Schedule a default pri and a low pri update on the root.
root.render(<Oops />);
React.startTransition(() => {
root.render(<AllGood />);
});
await expect(
act(async () => {
// Schedule a default pri and a low pri update on the root.
root.render(<Oops />);
React.startTransition(() => {
root.render(<AllGood />);
});

// Render through just the default pri update. The low pri update remains on
// the queue.
await waitFor(['Everything is fine.']);
// Render through the default pri and low pri update.
await waitFor(['Everything is fine.']);

// Schedule a discrete update on a child that triggers an error.
// The root should capture this error. But since there's still a pending
// update on the root, the error should be suppressed.
ReactNoop.discreteUpdates(() => {
setShouldThrow(true);
});
});
// Should render the final state without throwing the error.
assertLog(['Everything is fine.']);
expect(root).toMatchRenderedOutput('Everything is fine.');
// Schedule a discrete update on a child that triggers an error.
ReactNoop.discreteUpdates(() => {
setShouldThrow(true);
});
}),
).rejects.toThrow('Oops');
assertLog([]);
expect(root).toMatchRenderedOutput(null);
Copy link
Collaborator Author

Choose a reason for hiding this comment

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

Needs discussion. Might be fine since we already rendered the low-pri update before we error. So last render wins?

@eps1lon eps1lon force-pushed the error-boundary-double-commit-26518 branch 3 times, most recently from b3a570c to 2736d41 Compare April 21, 2024 22:56
@eps1lon eps1lon force-pushed the error-boundary-double-commit-26518 branch from 2736d41 to 00d183b Compare April 22, 2024 10:13
Comment on lines -279 to +285
expect(root).toMatchRenderedOutput('Count 0 (n=1)');
expect(Text).toBeCalledTimes(2);
if (gate(flags => flags.enableUnifiedSyncLane)) {
expect(root).toMatchRenderedOutput(<h1>Something went wrong.</h1>);
expect(Text).toBeCalledTimes(1);
} else {
expect(root).toMatchRenderedOutput('Count 0 (n=1)');
expect(Text).toBeCalledTimes(2);
}
Copy link
Collaborator Author

Choose a reason for hiding this comment

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

These assertions implicitly tested the error recovery. In concurrent updates we'd retry and this component only throws once:

if (shouldFail) {
  shouldFail = false;
  throw new Error('failed');
}

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.

None yet

4 participants