-
Notifications
You must be signed in to change notification settings - Fork 47.5k
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
Add dedicated LanePriority for Suspense retries #19287
Conversation
A "retry" is a special type of update that attempts to flip a Suspense boundary from its placeholder state back to its primary/resolved state. Currently, retries are given default priority, using the same algorithm as normal updates and occupying range of lanes. This adds a new range of lanes dedicated specifically to retries, and gives them lower priority than normal updates. A couple of existing tests were affected because retries are no longer batched with normal updates; they commit in separate batches. Not totally satisfied with this design, but I think things will snap more into place once the rest of the Lanes changes (like how we handle parallel Suspense transitions) have settled.
This pull request is automatically built and testable in CodeSandbox. To see build info of the built libraries, click here or the icon next to each commit SHA. Latest deployment of this branch, based on commit 723cfc8:
|
Details of bundled changes.Comparing: 40cddfe...723cfc8 react-dom
react-art
react-test-renderer
react-native-renderer
react-reconciler
ReactDOM: size: 0.0%, gzip: 🔺+0.1% Size changes (stable) |
Details of bundled changes.Comparing: 40cddfe...723cfc8 react-dom
react-art
react-native-renderer
react-test-renderer
react-reconciler
ReactDOM: size: 0.0%, gzip: 🔺+0.1% Size changes (experimental) |
packages/react-reconciler/src/__tests__/ReactSuspenseCallback-test.internal.js
Show resolved
Hide resolved
packages/react-reconciler/src/__tests__/ReactSuspenseWithNoopRenderer-test.js
Outdated
Show resolved
Hide resolved
// lanes, leading to the extra intermediate render. This is because when | ||
// we schedule the fourth update, we're already in the middle of rendering | ||
// the three others. Since three are only three lanes in the default | ||
// range, the fourth lane is shifted to slightly lower priority. This |
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.
the fourth lane is shifted to slightly lower priority
Could you expand in this more? I would think that the "priority" doesn't change, the work just gets pushed out further?
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.
The "lane priority" of a bit is determined by which range of bits it belongs to. We can't assign a new update to a lane that's already in the middle of rendering. In this (fairly contrived) test scenario, we ran out of lanes in the Default range so we had to pick from the next available lane in a lower priority range. Another option would be to throw out the in-progress work and start over — then we're free to pick any lane we want. tl;dr this is a batching heuristic that doesn't matter that much in most scenarios and probably isn't the last time this will change. It's only observable in tests if you do clever stuff like partial rendering, which we don't officially support and is only used in our own test suite.
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 think what's confusing to me in "the fourth lane is shifted to slightly lower priority" is that the lane isn't what changes priorities? Do you mean "the fourth update is shifted to a (lane in a) slightly lower priority"?
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.
Oh right. Yeah I meant fourth update :D
@@ -3923,7 +3923,6 @@ describe('Profiler', () => { | |||
|
|||
expect(onPostCommit).toHaveBeenCalledTimes(1); | |||
expect(onPostCommit.mock.calls[0][4]).toMatchInteractions([ | |||
initialRenderInteraction, |
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.
This is just gone?
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.
tl;dr the initial render is no longer entangled with the Suspense retry. See rationale from previous comment.
ec9f3d7
to
723cfc8
Compare
Note to whoever does this next sync: this is moderately risky. If I weren't possessed of such a daring personality, I might put it behind a feature flag. |
// from its placeholder state to its primary/resolved state. | ||
let lane = findLane(RetryRangeStart, RetryRangeEnd, wipLanes); | ||
if (lane === NoLane) { | ||
lane = pickArbitraryLane(RetryLanes); |
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.
Late to the party here (sorry, oncall this week and a bit behind) but catching up now, I had a small thought.
The naming of pickArbitraryLane
suggests (to me) that it could return a different value each time it's called. Its current implementation always happens to return a predictable/consistent value, but do we think this might ever change? If it did, the usage of it would be at conflict with the above comment:
This should be pure function, so that it always returns the same lane for given inputs.
...in which case maybe calling getLowestPriorityLane
directly would be "better"
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.
“arbitrary” in this case means it doesn’t matter if it’s the lowest priority lane or highest priority lane or anything in between.
For example, it communicates if there’s a refactor where the most significant lane is available for some other reason, you could use that instead without affecting the algorithm.
Now that Suspense retries have their own dedicated set of lanes (facebook#19287), we can determine if a render includes only retries by checking if its lanes are a subset of the retry lanes. Previously we inferred this by checking `workInProgressRootLatestProcessedEventTime`. If it's not set, that implies that no updates were processed in the current render, which implies it must be a Suspense retry. The eventual plan is to get rid of `workInProgressRootLatestProcessedEventTime` and instead track event times on the root; this change is one the steps toward that goal. The relevant tests were originally added in facebook#15769.
Now that Suspense retries have their own dedicated set of lanes (#19287), we can determine if a render includes only retries by checking if its lanes are a subset of the retry lanes. Previously we inferred this by checking `workInProgressRootLatestProcessedEventTime`. If it's not set, that implies that no updates were processed in the current render, which implies it must be a Suspense retry. The eventual plan is to get rid of `workInProgressRootLatestProcessedEventTime` and instead track event times on the root; this change is one the steps toward that goal. The relevant tests were originally added in #15769.
A "retry" is a special type of update that attempts to flip a Suspense boundary from its placeholder state back to its primary/resolved state.
Currently, retries are given default priority, using the same algorithm as normal updates and occupying range of lanes.
This adds a new range of lanes dedicated specifically to retries, and gives them lower priority than normal updates.
A couple of existing tests were affected because retries are no longer batched with normal updates; they commit in separate batches.
Not totally satisfied with this design, but I think things will snap more into place once the rest of the Lanes changes (like how we handle parallel Suspense transitions) have settled.