Join GitHub today
GitHub is home to over 50 million developers working together to host and review code, manage projects, and build software together.Sign up
GitHub is where the world builds software
Millions of developers and companies build, ship, and maintain their software on GitHub — the largest and most advanced development platform in the world.
This is ready for review. I'm going to do another pass to add comments and look for anything unusual I may have missed, but overall I'm happy with its current state.
All of the changes I've made in this PR are behind the
The only build that is affected is the
Because we have those protections in place, what I'm aiming for with this initial PR is the smallest possible atomic change that lands cleanly and doesn't rely on too many hacks. The goal has not been to get every single test or feature passing, and it definitely is not to implement all the features that we intend to build on top of the new model. When possible, I have chosen to preserve existing semantics and defer changes to follow-up steps. (Listed in the section below.)
(I did not end up having to disable any tests, although if I had, that should not have necessarily been a merge blocker.)
For example, even though one of the primary goals of this project is to improve our model for parallel Suspense transitions, in this initial implementation, I have chosen to keep the same core heuristics for sequencing and flushing that existed in the ExpirationTimes model: low priority updates cannot finish without also finishing high priority ones.
Despite all these precautions, because the scope of this refactor is inherently large, I do expect we will find regressions. The flip side is that I also expect the new model to improve the stability of the codebase and make it easier to fix bugs when they arise.
Technical description of Lanes
This is not a comprehensive description. That would take up many pages. What I've written here is a brief overview intended to help React team members translate from the old model (Expiration Times) to the new one (Lanes). I can write a longer technical document once more of follow-up steps done.
There are two primary advantages of the Lanes model over the Expiration Times model:
In the old model, to decide whether to include a given unit of work in the batch that's being worked on, we would compare their relative priorities:
const isTaskIncludedInBatch = priorityOfTask >= priorityOfBatch;
This worked because of a constraint we imposed where lower priority tasks were not allowed to complete unless higher priority tasks are also included. Given priorities A > B > C, you couldn't work on B without also working on A; nor could you work on C without working on both B and A.
This constraint was designed before Suspense was a thing, and it made some sense in that world. When all your work is CPU bound, there's not much reason to work on tasks in any order other than by their priority. But when you introduce tasks that are IO-bound (i.e. Suspense), you can have a scenario where a higher priority IO-bound task blocks a lower-priority CPU-bound task from completing.
A similar flaw of Expiration Times is that it's limited in how we can express a group of multiple priority levels.
Using a Set object isn't practical, in terms of either memory or computation — the existence checks we're dealing with are extremely pervasive, so they need to be fast and use as little memory as possible.
As a compromise, often what we'd do instead is maintain a range of priority levels:
const isTaskIncludedInBatch = taskPriority <= highestPriorityInRange && taskPriority >= lowestPriorityInRange;
Setting aside that this requires two separate fields, even this is quite limiting in its expressiveness. You can express a closed, continuous range of tasks. But you can't represent a finite set of distinct tasks. For example, given a range of tasks, how do you remove a task that lies in the middle of the range? Even in cases where we had designed a decent workaround, reasoning about groups of tasks this way became extremely confusing and prone to regressions.
At first by design, but then more by accident, the old model coupled the two concepts of 1) prioritization and 2) batching into a single data type. We were limited in our ability to express one except in terms that affected the other.
In the new model, we have decoupled those two concepts. Groups of tasks are instead expressed not as relative numbers, but as bitmasks:
const isTaskIncludedInBatch = (task & batchOfTasks) !== 0;
The type of the bitmask that represents a task is called a
In more concrete React terms, an update object scheduled by
On the other hand, a fiber is not associated with only a single update, but potentially many. So it has a
Lanes is an opaque type. You can only perform direct bitmask manipulation inside the
Commonly seen Expiration Time fields, translated to Lanes
Stuff I intentionally omitted from this initial PR
Things that probably block us from rolling this out at Facebook
Future improvements that will build on top
Not a comprehensive list
Details of bundled changes.
Size changes (experimental)
Details of bundled changes.
Size changes (stable)