[New Scheduler] Fix: Suspending an expired update #15326
Merged
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
When an async update expires, React renders at the expiration time that corresponds to the current time, not at the original update's expiration time. That way, all the expired work in the tree is flushed in a single batch.
This is implemented inside
renderRoot
by comparing the next render expiration time to the current time. If the current time is later,renderRoot
will restart at the later time.Because of poor factoring, the check is currently performed right before entering the work loop. But the work loop is usually entered multiple times in a single callback: each time a component throws or suspends. This led to an infinite loop where React would detect that an update expired, restart at the current time, make a bit of progress, suspend, check for expired work again, and start the loop again.
I fixed this by moving the expired work check to the beginning of
renderRoot
, so that it is not performed every time something suspends. This isn't ideal, because you could technically still fall into a loop if more than 10ms lapse in between exitingrenderRoot
and entering it again. The proper fix is to lift the check outside ofrenderRoot
entirely so that the function can restart without checking for expired work again. Since this is exceedingly unlikely (and this whole thing is still behind a flag), I'll do the better fix in an already-planned follow up to forkrenderRoot
into separate functions for sync and async work.