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

stash the component stack on the thrown value and reuse #25790

Merged
merged 1 commit into from
Feb 16, 2024

Conversation

gnoff
Copy link
Collaborator

@gnoff gnoff commented Dec 2, 2022

ErrorBoundaries are currently not fully composable. The reason is if you decide your boundary cannot handle a particular error and rethrow it to higher boundary the React runtime does not understand that this throw is a forward and it recreates the component stack from the Boundary position. This loses fidelity and is especially bad if the boundary is limited it what it handles and high up in the component tree.

This implementation uses a WeakMap to store component stacks for values that are objects. If an error is rethrown from an ErrorBoundary the stack will be pulled from the map if it exists. This doesn't work for thrown primitives but this is uncommon and stashing the stack on the primitive also wouldn't work

@sizebot
Copy link

sizebot commented Dec 2, 2022

Comparing: f0534ae...d1d6184

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.14% 154.40 kB 154.61 kB +0.11% 49.00 kB 49.05 kB
oss-experimental/react-dom/cjs/react-dom.production.min.js +0.13% 156.32 kB 156.53 kB +0.13% 49.65 kB 49.71 kB
facebook-www/ReactDOM-prod.classic.js +0.07% 533.40 kB 533.79 kB +0.13% 94.99 kB 95.12 kB
facebook-www/ReactDOM-prod.modern.js +0.07% 518.50 kB 518.89 kB +0.15% 92.80 kB 92.94 kB
facebook-www/ReactDOMForked-prod.classic.js +0.07% 533.40 kB 533.79 kB +0.13% 94.99 kB 95.12 kB

Significant size changes

Includes any change greater than 0.2%:

Expand to show
Name +/- Base Current +/- gzip Base gzip Current gzip
oss-stable-semver/react-test-renderer/umd/react-test-renderer.production.min.js +0.23% 100.44 kB 100.67 kB +0.31% 31.16 kB 31.26 kB
oss-stable/react-test-renderer/umd/react-test-renderer.production.min.js +0.23% 100.46 kB 100.69 kB +0.30% 31.16 kB 31.26 kB
oss-experimental/react-test-renderer/umd/react-test-renderer.production.min.js +0.23% 100.52 kB 100.75 kB +0.30% 31.19 kB 31.28 kB
oss-stable-semver/react-test-renderer/cjs/react-test-renderer.production.min.js +0.23% 100.20 kB 100.43 kB +0.22% 30.74 kB 30.81 kB
oss-stable/react-test-renderer/cjs/react-test-renderer.production.min.js +0.23% 100.22 kB 100.45 kB +0.21% 30.74 kB 30.81 kB
oss-experimental/react-test-renderer/cjs/react-test-renderer.production.min.js +0.23% 100.28 kB 100.51 kB +0.21% 30.78 kB 30.84 kB
oss-stable-semver/react-art/cjs/react-art.production.min.js +0.23% 92.33 kB 92.54 kB +0.19% 28.43 kB 28.49 kB
oss-stable/react-art/cjs/react-art.production.min.js +0.23% 92.35 kB 92.56 kB +0.19% 28.43 kB 28.49 kB
oss-experimental/react-art/cjs/react-art.production.min.js +0.22% 93.77 kB 93.98 kB +0.19% 28.87 kB 28.92 kB

Generated by 🚫 dangerJS against d1d6184

@poteto poteto added the React Core Team Opened by a member of the React Core Team label Dec 7, 2022
@@ -24,10 +25,24 @@ export function createCapturedValueAtFiber<T>(
): CapturedValue<T> {
// If the value is an error, call this function immediately after it is thrown
// so the stack is accurate.
let stack;
if (value != null && hasOwnProperty.call(value, '_componentStack')) {
Copy link
Collaborator

Choose a reason for hiding this comment

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

We probably should use a WeakMap instead to avoid any conflicts on the object.

Copy link
Collaborator

@sebmarkbage sebmarkbage left a comment

Choose a reason for hiding this comment

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

WeakMap and then good to land.

@gnoff gnoff force-pushed the retain-error-stacks branch from d1d6184 to 40a2cb9 Compare February 16, 2024 04:25
@react-sizebot
Copy link

react-sizebot commented Feb 16, 2024

Comparing: fea900e...3408207

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.10% 176.83 kB 177.01 kB +0.11% 55.11 kB 55.17 kB
oss-experimental/react-dom/cjs/react-dom.production.min.js +0.10% 178.83 kB 179.00 kB +0.09% 55.69 kB 55.74 kB
facebook-www/ReactDOM-prod.classic.js +0.07% 592.18 kB 592.58 kB +0.11% 104.43 kB 104.54 kB
facebook-www/ReactDOM-prod.modern.js +0.07% 575.46 kB 575.87 kB +0.12% 101.43 kB 101.55 kB
test_utils/ReactAllWarnings.js Deleted 66.25 kB 0.00 kB Deleted 16.31 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 66.25 kB 0.00 kB Deleted 16.31 kB 0.00 kB

Generated by 🚫 dangerJS against 3408207

@gnoff gnoff force-pushed the retain-error-stacks branch from 40a2cb9 to 3408207 Compare February 16, 2024 04:31
export function createCapturedValue<T>(
value: T,
export function createCapturedValueFromError(
value: Error,
Copy link
Collaborator Author

Choose a reason for hiding this comment

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

this was already being used on to construct add hoc captured values for the recoverable error pathway. It doesn't need value generality and this helps us avoid type checking the value before deciding if the stack needs to go in the map

): CapturedValue<T> {
): CapturedValue<Error> {
if (typeof stack === 'string') {
CapturedStacks.set(value, stack);
Copy link
Collaborator Author

Choose a reason for hiding this comment

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

The way this is used today we don't really need to capture the stack in the map because these errors aren't bubbling up during a render. However it could be used in other ways in the future so to keep it correct I'm stashing the stack here if it is provided.

@gnoff gnoff merged commit a9cc325 into facebook:main Feb 16, 2024
36 checks passed
@gnoff gnoff deleted the retain-error-stacks branch February 16, 2024 04:44
github-actions bot pushed a commit that referenced this pull request Feb 16, 2024
ErrorBoundaries are currently not fully composable. The reason is if you
decide your boundary cannot handle a particular error and rethrow it to
higher boundary the React runtime does not understand that this throw is
a forward and it recreates the component stack from the Boundary
position. This loses fidelity and is especially bad if the boundary is
limited it what it handles and high up in the component tree.

This implementation uses a WeakMap to store component stacks for values
that are objects. If an error is rethrown from an ErrorBoundary the
stack will be pulled from the map if it exists. This doesn't work for
thrown primitives but this is uncommon and stashing the stack on the
primitive also wouldn't work

DiffTrain build for [a9cc325](a9cc325)
huozhi added a commit to vercel/next.js that referenced this pull request Feb 23, 2024
### React upstream changes

- facebook/react#28333
- facebook/react#28334
- facebook/react#28378
- facebook/react#28377
- facebook/react#28376
- facebook/react#28338
- facebook/react#28331
- facebook/react#28336
- facebook/react#28320
- facebook/react#28317
- facebook/react#28375
- facebook/react#28367
- facebook/react#28380
- facebook/react#28368
- facebook/react#28343
- facebook/react#28355
- facebook/react#28374
- facebook/react#28362
- facebook/react#28344
- facebook/react#28339
- facebook/react#28353
- facebook/react#28346
- facebook/react#25790
- facebook/react#28352
- facebook/react#28326
- facebook/react#27688
- facebook/react#28329
- facebook/react#28332
- facebook/react#28340
- facebook/react#28327
- facebook/react#28325
- facebook/react#28324
- facebook/react#28309
- facebook/react#28310
- facebook/react#28307
- facebook/react#28306
- facebook/react#28315
- facebook/react#28318
- facebook/react#28226
- facebook/react#28308
- facebook/react#27563
- facebook/react#28297
- facebook/react#28286
- facebook/react#28284
- facebook/react#28275
- facebook/react#28145
- facebook/react#28301
- facebook/react#28224
- facebook/react#28152
- facebook/react#28296
- facebook/react#28294
- facebook/react#28279
- facebook/react#28273
- facebook/react#28269
- facebook/react#28376
- facebook/react#28338
- facebook/react#28331
- facebook/react#28336
- facebook/react#28320
- facebook/react#28317
- facebook/react#28375
- facebook/react#28367
- facebook/react#28380
- facebook/react#28368
- facebook/react#28343
- facebook/react#28355
- facebook/react#28374
- facebook/react#28362
- facebook/react#28344
- facebook/react#28339
- facebook/react#28353
- facebook/react#28346
- facebook/react#25790
- facebook/react#28352
- facebook/react#28326
- facebook/react#27688
- facebook/react#28329
- facebook/react#28332
- facebook/react#28340
- facebook/react#28327
- facebook/react#28325
- facebook/react#28324
- facebook/react#28309
- facebook/react#28310
- facebook/react#28307
- facebook/react#28306
- facebook/react#28315
- facebook/react#28318
- facebook/react#28226
- facebook/react#28308
- facebook/react#27563
- facebook/react#28297
- facebook/react#28286
- facebook/react#28284
- facebook/react#28275
- facebook/react#28145
- facebook/react#28301
- facebook/react#28224
- facebook/react#28152
- facebook/react#28296
- facebook/react#28294
- facebook/react#28279
- facebook/react#28273
- facebook/react#28269

Closes NEXT-2542


Disable ppr test for strict mode for now, @acdlite will check it and
we'll sync again
EdisonVan pushed a commit to EdisonVan/react that referenced this pull request Apr 15, 2024
ErrorBoundaries are currently not fully composable. The reason is if you
decide your boundary cannot handle a particular error and rethrow it to
higher boundary the React runtime does not understand that this throw is
a forward and it recreates the component stack from the Boundary
position. This loses fidelity and is especially bad if the boundary is
limited it what it handles and high up in the component tree.

This implementation uses a WeakMap to store component stacks for values
that are objects. If an error is rethrown from an ErrorBoundary the
stack will be pulled from the map if it exists. This doesn't work for
thrown primitives but this is uncommon and stashing the stack on the
primitive also wouldn't work
bigfootjon pushed a commit that referenced this pull request Apr 18, 2024
ErrorBoundaries are currently not fully composable. The reason is if you
decide your boundary cannot handle a particular error and rethrow it to
higher boundary the React runtime does not understand that this throw is
a forward and it recreates the component stack from the Boundary
position. This loses fidelity and is especially bad if the boundary is
limited it what it handles and high up in the component tree.

This implementation uses a WeakMap to store component stacks for values
that are objects. If an error is rethrown from an ErrorBoundary the
stack will be pulled from the map if it exists. This doesn't work for
thrown primitives but this is uncommon and stashing the stack on the
primitive also wouldn't work

DiffTrain build for commit a9cc325.
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.

6 participants