Skip to content
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

Initial Lanes implementation #18796

Merged
merged 2 commits into from
May 3, 2020
Merged

Initial Lanes implementation #18796

merged 2 commits into from
May 3, 2020

Conversation

acdlite
Copy link
Collaborator

@acdlite acdlite commented May 1, 2020

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 enableNewReconciler flag. Merging this to master will not affect the open source builds or the build that we ship to Facebook.

The only build that is affected is the ReactDOMForked build, which is deployed to Facebook behind an experimental flag (currently disabled for all users). We will use this flag to gradually roll out the new reconciler, and quickly roll it back if we find any problems.

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:

  • Lanes decouple the concept of task prioritization ("Is task A higher priority than task B?") from task batching ("Is task A part of this group of tasks?").
  • Lanes can express many distinct task threads with a single, 32-bit data type.

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 Lane. The type of the bitmask that represents a batch is called Lanes. (Note: these names are not final. I know using a plural is a bit confusing, but since the name appears all over the place, I wanted something that was short. But I'm open to suggestions.)

In more concrete React terms, an update object scheduled by setState contains a lane field, a bitmask with a single bit enabled. This replaces the update.expirationTime field in the old model.

On the other hand, a fiber is not associated with only a single update, but potentially many. So it has a lanes field, a bitmask with zero or more bits enabled (fiber.expirationTime in the old model); and a childLanes field (fiber.childExpirationTime).

Lanes is an opaque type. You can only perform direct bitmask manipulation inside the ReactFiberLane module. Elsewhere, you must import a helper function from that module. This is a trade off, but one that I think is ultimately worth it, since dealing with Lanes can be very subtle, and colocating all the logic will make it easier for us to tweak our heuristics without having to do a huge refactor (like this one) every time.

Commonly seen Expiration Time fields, translated to Lanes

  • renderExpirationtime -> renderLanes
  • update.expirationTime -> update.lane
  • fiber.expirationTime -> fiber.lanes
  • fiber.childExpirationTime -> fiber.childLanes
  • root.firstPendingTime and root.lastPendingTime -> fiber.pendingLanes

Stuff I intentionally omitted from this initial PR

Things that probably block us from rolling this out at Facebook

  • Protection against starvation by high pri React updates. If one update is interrupted by another, that update will never expire. We do get starvation protection from non-React work, via the Scheduler package. However, we shouldn't rely on that, either, since the native Scheduler may or may not have a timeout mechanism.

Future improvements that will build on top

Not a comprehensive list

  • Entanglement, a mechanism to enforce that a lane depends on another one finishing in the same batch. Improvements that will be unlocked by this feature include:
    • Skipping ("canceling") intermediate useTransition states. Only the most recent transition in a sequence of related transitions should be allowed to finish.
    • De-opting useMutableSource less frequently.
  • Working on lanes out of order. This will require us to design a new model for retrying suspended lanes when data is received, since it's infeasible to try every possible combination. Improvements that will be unlocked by this feature include:
    • Parallel UI transitions via useTransition. Currently, a single transition can easily block other transitions from finishing, even if they are in totally unrelated parts of the tree.
    • Arbitrarily long-lived transitions that run in the background, like for refreshing data. Related to previous item.
  • Eagerly computing state updates scheduled via the useState hook. We may do this via entanglement, or we may take advantage of the fact that there are a finite number of non-overlapping batches that an update might render into and precompute each one whenever an additional pending update is scheduled before the queue is flushed.

@facebook-github-bot facebook-github-bot added CLA Signed React Core Team Opened by a member of the React Core Team labels May 1, 2020
@codesandbox-ci
Copy link

codesandbox-ci bot commented May 1, 2020

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 1114b6e:

Sandbox Source
vigilant-hopper-omi4i Configuration

@sizebot
Copy link

sizebot commented May 1, 2020

Details of bundled changes.

Comparing: 3c7d52c...1114b6e

react-dom

File Filesize Diff Gzip Diff Prev Size Current Size Prev Gzip Current Gzip ENV
react-dom-test-utils.development.js 0.0% -0.0% 75.34 KB 75.34 KB 20.19 KB 20.19 KB UMD_DEV
react-dom-unstable-fizz.browser.development.js 0.0% +0.2% 5.36 KB 5.36 KB 1.8 KB 1.81 KB UMD_DEV
ReactDOMTesting-dev.js 0.0% -0.0% 910.16 KB 910.16 KB 203.18 KB 203.18 KB FB_WWW_DEV
react-dom-server.browser.development.js 0.0% -0.0% 162.26 KB 162.26 KB 41.29 KB 41.29 KB UMD_DEV
ReactDOMForked-dev.js -0.2% +0.6% 1012.9 KB 1010.39 KB 225.47 KB 226.83 KB FB_WWW_DEV
ReactDOMForked-prod.js -1.8% 🔺+0.1% 424.87 KB 417.15 KB 74.38 KB 74.48 KB FB_WWW_PROD
ReactDOMForked-profiling.js -1.9% +0.1% 435.21 KB 426.84 KB 76.08 KB 76.13 KB FB_WWW_PROFILING
react-dom-server.browser.production.min.js 0.0% 0.0% 23.32 KB 23.32 KB 8.6 KB 8.6 KB NODE_PROD
react-dom-unstable-fizz.node.development.js 0.0% +0.1% 5.61 KB 5.61 KB 1.86 KB 1.87 KB NODE_DEV
react-dom-unstable-fizz.node.production.min.js 0.0% -0.3% 1.17 KB 1.17 KB 668 B 666 B NODE_PROD
ReactDOMTesting-prod.js 0.0% -0.0% 378.9 KB 378.9 KB 69.16 KB 69.15 KB FB_WWW_PROD
react-dom-test-utils.development.js 0.0% -0.0% 70.17 KB 70.17 KB 19.69 KB 19.68 KB NODE_DEV
react-dom-test-utils.production.min.js 0.0% 0.0% 13.12 KB 13.12 KB 4.81 KB 4.81 KB NODE_PROD
react-dom-server.node.production.min.js 0.0% 0.0% 23.73 KB 23.73 KB 8.75 KB 8.75 KB NODE_PROD

Size changes (experimental)

Generated by 🚫 dangerJS against 1114b6e

@sizebot
Copy link

sizebot commented May 1, 2020

Details of bundled changes.

Comparing: 3c7d52c...1114b6e

react-dom

File Filesize Diff Gzip Diff Prev Size Current Size Prev Gzip Current Gzip ENV
react-dom-test-utils.production.min.js 0.0% 0.0% 13.1 KB 13.1 KB 4.8 KB 4.8 KB NODE_PROD
react-dom-unstable-fizz.node.production.min.js 0.0% -0.2% 1.16 KB 1.16 KB 659 B 658 B NODE_PROD
ReactDOMForked-dev.js -0.2% +0.6% 1.01 MB 1.01 MB 231.03 KB 232.46 KB FB_WWW_DEV
ReactDOMForked-prod.js -1.8% 🔺+0.2% 436.42 KB 428.73 KB 76.4 KB 76.52 KB FB_WWW_PROD
ReactDOMForked-profiling.js -1.9% +0.1% 446.83 KB 438.48 KB 78.09 KB 78.17 KB FB_WWW_PROFILING
ReactDOMTesting-dev.js 0.0% 0.0% 935.95 KB 935.95 KB 208.72 KB 208.72 KB FB_WWW_DEV
react-dom.development.js 0.0% -0.0% 864.17 KB 864.17 KB 197.14 KB 197.14 KB NODE_DEV
react-dom-test-utils.development.js 0.0% -0.0% 75.32 KB 75.32 KB 20.18 KB 20.18 KB UMD_DEV
react-dom-test-utils.production.min.js 0.0% 0.0% 13.23 KB 13.23 KB 4.89 KB 4.89 KB UMD_PROD
ReactDOM-prod.js 0.0% -0.0% 433.01 KB 433.01 KB 75.88 KB 75.88 KB FB_WWW_PROD
react-dom-test-utils.development.js 0.0% -0.0% 70.16 KB 70.16 KB 19.68 KB 19.67 KB NODE_DEV
react-dom-unstable-fizz.node.development.js 0.0% +0.1% 5.6 KB 5.6 KB 1.86 KB 1.86 KB NODE_DEV
react-dom-server.browser.development.js 0.0% -0.0% 145.79 KB 145.79 KB 38.72 KB 38.72 KB NODE_DEV
react-dom-unstable-fizz.browser.development.js 0.0% +0.2% 5.35 KB 5.35 KB 1.79 KB 1.8 KB UMD_DEV
ReactDOMServer-dev.js 0.0% -0.0% 163.85 KB 163.85 KB 41.76 KB 41.76 KB FB_WWW_DEV
react-dom-unstable-fizz.browser.development.js 0.0% +0.1% 4.85 KB 4.85 KB 1.7 KB 1.7 KB NODE_DEV

Size changes (stable)

Generated by 🚫 dangerJS against 1114b6e


function getLowestPriorityLane(lanes: Lanes): Lane {
// This finds the most significant non-zero bit.
// TODO: Consider alternate implementations. Could use a map. Is Math.log2

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think Math.clz32 could work!

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Oh I didn’t realize so many browsers supported this. We can use a fallback when it’s not there. Good idea, thanks!

'b',
'c',

// The old reconciler has a quirk where `e` d has slightly lower
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

nit: typo "e d"

'b',
'c',

// The old reconciler has a quirk where `e` d has slightly lower
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

nit: typo "e d"

'setState updater',
// In the new reconciler, updates inside the render phase are
// treated as if they came from an event, so the update gets
// shifted to a subsequent render.
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Subsequent render+commit?

What are the implications for e.g. useSubscription?

// If parameters have changed since our last render, schedule an update with its current value.
if (
state.getCurrentValue !== getCurrentValue ||
state.subscribe !== subscribe
) {
// If the subscription has been updated, we'll schedule another update with React.
// React will process this update immediately, so the old subscription value won't be committed.
// It is still nice to avoid returning a mismatched value though, so let's override the return value.
valueToReturn = getCurrentValue();
setState({
getCurrentValue,
subscribe,
value: valueToReturn,
});
}

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

useSubscription doesn’t call setState during the render phase right? I think the only place we do this intentionally is useOpaqueIdentifier. Everywhere else we warn.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The code I linked to above is useSubscription calling setState during the render phase, yes.

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ah that's a hook update on itself. Those are fine. Should come up with some disambiguating term.

"Render phase" updates here refers to updates on another component, or a class component on itself. The ones that would trigger the warnAboutRenderPhaseUpdatesInDEV warning.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ah! Ok thanks for clarifying 😄

// The pending update priority was cleared at the beginning of
// beginWork. We're about to bail out, but there might be additional
// updates at a lower priority. Usually, the priority level of the
if (!includesSomeLane(renderLanes, updateLanes)) {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

😍

? prevBaseTime
: renderExpirationTime;
const prevBaseLanes = prevState.baseLanes;
nextBaseLanes = combineLanes(prevBaseLanes, renderLanes);
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

You missed an opportunity to call this mergeLanes

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It’s never too late

@@ -249,7 +250,7 @@ export function updateContainer(
}
}
const suspenseConfig = requestCurrentSuspenseConfig();
const expirationTime = requestUpdateExpirationTime(current, suspenseConfig);
const expirationTime = requestUpdateLane(current, suspenseConfig);
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
const expirationTime = requestUpdateLane(current, suspenseConfig);
const lane = requestUpdateLane(current, suspenseConfig);

@@ -500,34 +468,33 @@ export function scheduleUpdateOnFiber(
// This is the result of a discrete event. Track the lowest priority
// discrete update per root so we can flush them early, if needed.
if (rootsWithPendingDiscreteUpdates === null) {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This if/else needs to move up into the else above now (see PR #18797)

startWorkOnPendingInteractions(root, lanes);
} else if (lanes !== root.wipLanes) {
startWorkOnPendingInteractions(root, lanes);
prepareFreshStack(root, lanes);
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Calling prepareFreshStack after doing something looks odd. Can you explain why we do it?

Copy link
Collaborator Author

@acdlite acdlite May 1, 2020

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We call both of these functions right before starting a new render at a new level. I don't think the order matters since prepareFreshStack doesn't touch root.memoizedInteractions, but I'll rearrange them for consistency with the other branches.

@@ -2769,9 +2532,14 @@ export function pingSuspendedRoot(
pingCache.delete(wakeable);
}

// TODO: Instead of passing the current time as an argument, we should read it
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

nit "time"


let subscriber;

try {
subscriber = __subscriberRef.current;
if (subscriber !== null && root.memoizedInteractions.size > 0) {
const threadID = computeThreadID(root, committedExpirationTime);
// FIXME: More than one lane can finish in a single commit.
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

How often do you think this will break things?

Copy link
Collaborator Author

@acdlite acdlite May 1, 2020

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I wasn’t sure about this. If it’s not already a problem then this doesn’t make it worse, though, since multiple expirations times could finish in the same batch, too.

@bvaughn
Copy link
Contributor

bvaughn commented May 1, 2020

This change looks great, by the way. Really excited about the new direction.

const SyncBatchedUpdateRangeEnd = 2;

export const InputDiscreteHydrationLane: Lane = /* */ 0b000000000000000000000000000100;
const InputDiscreteLanes: Lanes = /* */ 0b000000000000000000000000011100;
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Maybe silly question: what are there three lanes for this as opposed to just two?

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

One is for a trick we use during hydration. If we receive an update to a tree that hasn’t been hydrated yet, we restart at a slightly higher priority to hydrate it, then continue with the update on top. So every async priority has a hydration lane associated with it.

The other extra lane was reserved for a trick that SuspenseList does where it leaves behind work that is slightly lower priority, but that turned out to not be necessary, so I removed it: #18738

Regardless, this whole range is going to go away when we make discrete inputs synchronous, so we can redistribute them to other priorities.

const IdleUpdateRangeStart = 27;
const IdleUpdateRangeEnd = 29;

export const OffscreenLane: Lane = /* */ 0b100000000000000000000000000000;
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

As a heads up, @sebmarkbage and I were thinking about renaming Offscreen to something else (like maybe Shelf). Not sure if that will affect your naming

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

"shelf"? 🤨 I'd be interested in hearing the reasoning behind this! 😆 Offscreen seemed pretty clear.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Seb thinks that having an Offscreen component when something is not off screen is confusing. He is worried that people might misunderstand the name and assume that you only need to use Offscreen component if the elements are offscreen, which might result in people not wrapping something with Offscreen until the elements are hidden.

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

<KeepAlive>, a la Vue?

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think "shelf" might be a bit of a colloquialism that isn't very intuitive for a lot of people.

Isn't Offscreen always for things that might be on or off the screen? (e.g. list items that might be scrolled out of viewports, tab containers that might be hidden)

Is the concern that you'd always have an Offscreen wrapper around even the visible items in that case (since we don't support reparenting)?

Maybe something like Conditional would be more accurate/intuitive?

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It does seem related to the concept of keep-alive...

Copy link
Collaborator Author

@acdlite acdlite May 2, 2020

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Agree that a public API called Offscreen is confusing. Similar to when we were trying to think of a name for the Suspense component.

The naming considerations are slightly different for this lane, though. The thing that makes OffscreenLane special, aside from it being really low priority, is that we're allowed to not finish it (i.e. it can tear), because the work at that level is not visible to the user.

// TODO: Move this check out of the hot path by moving `resetChildLanes`
// to switch statement in `completeWork`.
(completedWork.tag === LegacyHiddenComponent ||
completedWork.tag === OffscreenComponent) &&
completedWork.memoizedState !== null &&
!includesSomeLane(renderLanes, (OffscreenLane: Lane))

A better solution, though, might be to move resetChildLanes (the function in that snippet) to the switch statement inside completeWork. Then the only thing remarkable about OffscreenLane is that it has the lowest priority.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

<MaybeOrNotOffscreen /> 😆

}

export function getLanesToRetrySynchronouslyOnError(root: FiberRoot): Lanes {
const everythingButOffscreen = root.pendingLanes & ~OffscreenLane;
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why are we retrying IdleLanes if we are not retrying OffscreenLanes? I tried to grep to see where we were using IdleLands outside of schedulerPriorityToLanePriority and I couldn't find other use cases, so I might be missing something here.

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The idea is to include anything that might include a pending update that would fix the error that we’re trying to recover from. Offscreen never includes updates, only deferred subtrees.

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Another way to look at it is: we don't want to include Offscreen because that work is expensive; for example, it includes hydrating the remaining dehydrated boundaries. So since we know including Offscreen won't fix the error, anyway, might as well exclude it.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

That makes sense. What type of work would fall under IdleLanes then?

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Updates that don't have to finish with any particular urgency. Like maybe a background refresh of data. Honestly, we don't have a ton of use cases in practice yet. It might end up merging with the "long transition" lanes.

__DEV__
? [
'Child with ID',
// Fallbacks are immdiately committed in TestUtils version

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Spelling: immediately

@acdlite acdlite force-pushed the initial-lanes branch 4 times, most recently from 53c3611 to 4689b2c Compare May 2, 2020 23:20
These tests were written in such a way that when a wakeable has multiple
listeners, only the most recent listener is notified.

I switched them to use a promise instead of mutating a ref.
A refactor of our concurrency and scheduling architecture. Replaces
the ExpirationTime type with a new concept, called Lanes.

See PR facebook#18796 for more information.

All of the changes I've made in this commit are behind the
`enableNewReconciler` flag. Merging this to master will not affect the
open source builds or the build that we ship to Facebook.

The only build that is affected is the `ReactDOMForked` build, which is
deployed to Facebook **behind an experimental flag (currently disabled
for all users)**. We will use this flag to gradually roll out the new
reconciler, and quickly roll it back if we find any problems.

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.
@acdlite acdlite force-pushed the initial-lanes branch 2 times, most recently from 3d0790a to 1114b6e Compare May 3, 2020 00:00
@acdlite acdlite merged commit 93e078d into facebook:master May 3, 2020
// bother waiting until the root is complete.
(wipLanes & suspendedLanes) === NoLanes
) {
getHighestPriorityLanes(wipLanes);
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It's a 'get' function but we don't use the return value from it though that's intended, but is that will get confusing?


export function higherPriorityLane(a: Lane, b: Lane) {
// This works because the bit ranges decrease in priority as you go left.
return a !== NoLane && a < b ? a : b;
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is it possible that b equals to NoLane?

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

In practice no, it's always SelectiveHydrationLane but should probably rename/refactor this to something more specific so it's not used elsewhere.

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Good catch :)

return everythingButOffscreen;
}
if (everythingButOffscreen & OffscreenLane) {
return OffscreenLane;
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Seems this branch is unreachable. Maybe root.pendingLanes & OffscreenLane?

@Knove
Copy link

Knove commented Sep 9, 2020

i need a longer technical document :P~

@awefeng
Copy link

awefeng commented Dec 12, 2020

Maybe I need an article about lanes to explain 5W1H,bigwig~~~~~

@NotCoderJack
Copy link

is there an longer technical document about lanes ? Really need it! @acdlite

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
CLA Signed React Core Team Opened by a member of the React Core Team
Projects
None yet
Development

Successfully merging this pull request may close these issues.