-
Notifications
You must be signed in to change notification settings - Fork 49.9k
[Flight][Fiber] Encode owner in the error payload in dev and use it as the Error's Task #34460
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
Conversation
| function warnOnSymbolType(returnFiber: Fiber, invalidChild: symbol) { | ||
| const debugTask = getCurrentDebugTask(); | ||
| if (__DEV__ && debugTask !== null) { | ||
| debugTask.run(warnOnSymbolTypeImpl.bind(null, returnFiber, invalidChild)); |
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.
Technically these should probably use this technique instead of creating a fake Fiber that gets logged:
So that captureOwnerStack inside console.error can also observe it.
But I'm not bothering with that for now.
It doesn't matter for the throws since Error constructor doesn't observe it anyway.
…s the Error's Task (#34460) When we report an error we typically log the owner stack of the thing that caught the error. Similarly we restore the `console.createTask` scope of the catching component when we call `reportError` or `console.error`. We also have a special case if something throws during reconciliation which uses the Server Component task as far as we got before we threw. https://github.com/facebook/react/blob/main/packages/react-reconciler/src/ReactChildFiber.js#L1952-L1960 Chrome has since fixed it (on our request) that the Error constructor snapshots the Task at the time the constructor was created and logs that in `reportError`. This is a good thing since it means we get a coherent stack. Unfortunately, it means that the fake Errors that we create in Flight Client gets a snapshot of the task where they were created so when they're reported in the console they get the root Task instead of the Task of the handler of the error. Ideally we'd transfer the Task from the server and restore it. However, since we don't instrument the Error object to snapshot the owner and we can't read the native Task (if it's even enabled on the server) we don't actually have a correct snapshot to transfer for a Server Component Error. However, we can use the parent's task for where the error was observed by Flight Server and then encode that as a pseudo owner of the Error. Then we use this owner as the Task which the Error is created within. Now the client snapshots that Task which is reported by `reportError` so now we have an async stack for Server Component errors again. (Note that this owner may differ from the one observed by `captureOwnerStack` which gets the nearest Server Component from where it was caught. We could attach the owner to the Error object and use that owner when calling `onCaughtError`/`onUncaughtError`). Before: <img width="911" height="57" alt="Screenshot 2025-09-10 at 10 57 54 AM" src="https://github.com/user-attachments/assets/0446ef96-fad9-4e17-8a9a-d89c334233ec" /> After: <img width="910" height="128" alt="Screenshot 2025-09-10 at 11 06 20 AM" src="https://github.com/user-attachments/assets/b30e5892-cf40-4246-a588-0f309575439b" /> Similarly, there are Errors and warnings created by ChildFiber itself. Those execute in the scope of the general render of the parent Fiber. They used to get the scope of the nearest client component parent (e.g. div in this case) but that's the parent of the Server Component. It would be too expensive to run every level of reconciliation in its own task optimistically, so this does it only when we know that we'll throw or log an error that needs this context. Unfortunately this doesn't cover user space errors (such as if an iterable errors). Before: <img width="903" height="298" alt="Screenshot 2025-09-10 at 11 31 55 AM" src="https://github.com/user-attachments/assets/cffc94da-8c14-4d6e-9a5b-bf0833b8b762" /> After: <img width="1216" height="252" alt="Screenshot 2025-09-10 at 11 50 54 AM" src="https://github.com/user-attachments/assets/f85f93cf-ab73-4046-af3d-dd93b73b3552" /> <img width="412" height="115" alt="Screenshot 2025-09-10 at 11 52 46 AM" src="https://github.com/user-attachments/assets/a76cef7b-b162-4ecf-9b0a-68bf34afc239" /> DiffTrain build for [20e5431](20e5431)
…s the Error's Task (#34460) When we report an error we typically log the owner stack of the thing that caught the error. Similarly we restore the `console.createTask` scope of the catching component when we call `reportError` or `console.error`. We also have a special case if something throws during reconciliation which uses the Server Component task as far as we got before we threw. https://github.com/facebook/react/blob/main/packages/react-reconciler/src/ReactChildFiber.js#L1952-L1960 Chrome has since fixed it (on our request) that the Error constructor snapshots the Task at the time the constructor was created and logs that in `reportError`. This is a good thing since it means we get a coherent stack. Unfortunately, it means that the fake Errors that we create in Flight Client gets a snapshot of the task where they were created so when they're reported in the console they get the root Task instead of the Task of the handler of the error. Ideally we'd transfer the Task from the server and restore it. However, since we don't instrument the Error object to snapshot the owner and we can't read the native Task (if it's even enabled on the server) we don't actually have a correct snapshot to transfer for a Server Component Error. However, we can use the parent's task for where the error was observed by Flight Server and then encode that as a pseudo owner of the Error. Then we use this owner as the Task which the Error is created within. Now the client snapshots that Task which is reported by `reportError` so now we have an async stack for Server Component errors again. (Note that this owner may differ from the one observed by `captureOwnerStack` which gets the nearest Server Component from where it was caught. We could attach the owner to the Error object and use that owner when calling `onCaughtError`/`onUncaughtError`). Before: <img width="911" height="57" alt="Screenshot 2025-09-10 at 10 57 54 AM" src="https://github.com/user-attachments/assets/0446ef96-fad9-4e17-8a9a-d89c334233ec" /> After: <img width="910" height="128" alt="Screenshot 2025-09-10 at 11 06 20 AM" src="https://github.com/user-attachments/assets/b30e5892-cf40-4246-a588-0f309575439b" /> Similarly, there are Errors and warnings created by ChildFiber itself. Those execute in the scope of the general render of the parent Fiber. They used to get the scope of the nearest client component parent (e.g. div in this case) but that's the parent of the Server Component. It would be too expensive to run every level of reconciliation in its own task optimistically, so this does it only when we know that we'll throw or log an error that needs this context. Unfortunately this doesn't cover user space errors (such as if an iterable errors). Before: <img width="903" height="298" alt="Screenshot 2025-09-10 at 11 31 55 AM" src="https://github.com/user-attachments/assets/cffc94da-8c14-4d6e-9a5b-bf0833b8b762" /> After: <img width="1216" height="252" alt="Screenshot 2025-09-10 at 11 50 54 AM" src="https://github.com/user-attachments/assets/f85f93cf-ab73-4046-af3d-dd93b73b3552" /> <img width="412" height="115" alt="Screenshot 2025-09-10 at 11 52 46 AM" src="https://github.com/user-attachments/assets/a76cef7b-b162-4ecf-9b0a-68bf34afc239" /> DiffTrain build for [20e5431](20e5431)
When we report an error we typically log the owner stack of the thing that caught the error. Similarly we restore the
console.createTaskscope of the catching component when we callreportErrororconsole.error.We also have a special case if something throws during reconciliation which uses the Server Component task as far as we got before we threw.
https://github.com/facebook/react/blob/main/packages/react-reconciler/src/ReactChildFiber.js#L1952-L1960
Chrome has since fixed it (on our request) that the Error constructor snapshots the Task at the time the constructor was created and logs that in
reportError. This is a good thing since it means we get a coherent stack. Unfortunately, it means that the fake Errors that we create in Flight Client gets a snapshot of the task where they were created so when they're reported in the console they get the root Task instead of the Task of the handler of the error.Ideally we'd transfer the Task from the server and restore it. However, since we don't instrument the Error object to snapshot the owner and we can't read the native Task (if it's even enabled on the server) we don't actually have a correct snapshot to transfer for a Server Component Error. However, we can use the parent's task for where the error was observed by Flight Server and then encode that as a pseudo owner of the Error.
Then we use this owner as the Task which the Error is created within. Now the client snapshots that Task which is reported by
reportErrorso now we have an async stack for Server Component errors again. (Note that this owner may differ from the one observed bycaptureOwnerStackwhich gets the nearest Server Component from where it was caught. We could attach the owner to the Error object and use that owner when callingonCaughtError/onUncaughtError).Before:
After:
Similarly, there are Errors and warnings created by ChildFiber itself. Those execute in the scope of the general render of the parent Fiber. They used to get the scope of the nearest client component parent (e.g. div in this case) but that's the parent of the Server Component. It would be too expensive to run every level of reconciliation in its own task optimistically, so this does it only when we know that we'll throw or log an error that needs this context. Unfortunately this doesn't cover user space errors (such as if an iterable errors).
Before:
After: