-
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
Recover from errors with a boundary in completion phase #14104
Conversation
Hmm. I was too confident. This causes some infinite loops. |
ReactDOM: size: 0.0%, gzip: 0.0% Details of bundled changes.Comparing: b305c4e...22158a5 react-dom
react-art
react-test-renderer
react-reconciler
react-native-renderer
scheduler
Generated by 🚫 dangerJS |
Pushed a different fix. This doesn't fail any tests but is awkward. |
This is less awkward. Ready for review. Still don't know if it's right approach. |
const failedUnitOfWork: Fiber = nextUnitOfWork; | ||
if (__DEV__ && replayFailedUnitOfWorkWithInvokeGuardedCallback) { | ||
replayUnitOfWork(failedUnitOfWork, thrownValue, isYieldy); | ||
} |
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.
Does this mean that the bug only happens when in DEV?
Also, couldn't this code be combined into the following block to avoid implying that the conditions/variables have any effect in production?
if (__DEV__) {
// Reset global debug state
// We assume this is defined in DEV
(resetCurrentlyProcessingQueue: any)();
if (!wasCompleting && replayFailedUnitOfWorkWithInvokeGuardedCallback) {
const failedUnitOfWork: Fiber = nextUnitOfWork;
replayUnitOfWork(failedUnitOfWork, thrownValue, isYieldy);
}
}
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.
Thanks. I pushed another commit that clarifies which parts of the fix are DEV-only.
Maybe we should fix this part instead? |
Rephrased, here's the alternate fix I'm suggesting. Either:
|
Isn’t that what I’m doing? |
Oh right, I read the first few commits and then didn't realize the subsequent ones changed it. :D |
// Reset in case completion throws. | ||
// This is only used in DEV and when replaying is on. | ||
const mayReplay = mayReplayFailedUnitOfWork; | ||
mayReplayFailedUnitOfWork = true; |
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 don't love this boolean, especially because it's not dev only. But I'm more concerned that this replay logic is getting really convoluted and it seems there must be a better way to structure it.
Maybe we can move where replayUnitOfWork
is called instead. If replayUnitOfWork
is only called after a failed begin phase, we could fork beginWork
to beginWorkInDEV
which has a local try/catch.
function beginWorkInDEV(current, workInProgress, renderExpirationTime) {
try {
beginWork(current, workInProgress, renderExpirationTime);
} catch (e) {
resetContextDependences();
resetHooks();
// Either call replayUnitOfWork or inline the whole thing here
// Then throw to outer catch block
throw e;
}
}
There might be other implications that I'm forgetting, but I think we should give this a shot before piling more complexity onto this code path.
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.
especially because it's not dev only
It is though. I think it would get DCE’d because we never read it outside DEV. The only reason I write to it outside a flag is just because it seems awkward to do extra wrapping because of scoping.
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.
Just checked — this is what I originally did, but @sebmarkbage asked that I move the extra try
out of the hot path: #12201 (comment)
I feel we should reconsider given the additional complexity of distinguishing between the begin and complete phases.
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.
Pushed another commit that makes it clearer that part is DEV-only too. I'll look at your suggestion tomorrow/
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.
Ok let's land this now so we can include the bugfix in the next patch release. Since this isn't the first time we've had a bug related to this part of the error-handling path, let's revisit soon to see if there's a better way factor it.
let's |
* Recover from errors with a boundary in completion phase * Use a separate field for completing unit of work * Use a simpler fix with one boolean * Reoder conditions * Clarify which paths are DEV-only * Move duplicated line out * Make it clearer this code is DEV-only
* Recover from errors with a boundary in completion phase * Use a separate field for completing unit of work * Use a simpler fix with one boolean * Reoder conditions * Clarify which paths are DEV-only * Move duplicated line out * Make it clearer this code is DEV-only
Fixes #13820.
Dunno if this is an "idiomatic" fix. The issue is that we null out
nextUnitOfWork
as we traverse up during completion, but we also don't attempt to find an error boundary ifnextUnitOfWork
is null:react/packages/react-reconciler/src/ReactFiberScheduler.js
Lines 1280 to 1284 in b305c4e
So errors in completion phase don't look for boundaries at all right now.