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

[Fiber] Separate priority field for pending updates #7457

Closed
wants to merge 21 commits into from

Conversation

acdlite
Copy link
Collaborator

@acdlite acdlite commented Aug 10, 2016

Currently there is a single priority field pendingWorkPriority that represents the priority of the entire subtree. The scheduler does a breadth-first search to find the next unit of work with a matching priority level. If a subtree's priority does not match, that entire subtree is skipped. That means when an update is scheduled deep in the tree (setState), its priority must be bubbled to the top of the tree. The problem is that this causes the all the parent priorities to be overridden — there's no way to schedule a high priority update inside a low priority tree without increasing the priority of the entire tree.

To address this, this PR introduces a separate priority field called pendingUpdatePriority (not 100% sold on the name). The new field represents the priority of the pending props and state, which may be lower than the priority of the entire tree. Now, when the scheduler is searching for work to perform, it only matches if the pendingUpdatePriority matches. It continues to use the pendingWorkPriority as a fast hint to skip subtrees if they do not match. (To elaborate further: because the work priority is the highest priority of any node in the entire tree, if it does not match, there's no point in searching any deeper.)

Based on #7344. Compare view: acdlite/react@fibersetstate...fiberupdatepriority

@sebmarkbage

sebmarkbage and others added 20 commits September 1, 2016 20:36
The priority level gets reset at the wrong time because I rely
on mutating it at the wrong point. This moves it to the end of
completed work as a second pass over the children to see what
the highest priority is. This is inefficient for large sets but
we can try to find a smarter way to do this later. E.g. passing
it down the stack.

This bug fix revealed another bug that I had flagged before that
we're finding work to do in the "current" tree instead of the
working tree. For trees that were paused, e.g. childInProgress
trees, this won't work since don't have a current tree to search.

Therefore I fixed findNextUnitOfWorkAtPriority to use
workInProgress instead of current.
The findPendingWork phase is the same as just walking the tree the
normal way effectively. It only makes things more complex to
think about. We might possibly be able to write a few special
branches to optimize it but for now it doesn't seem necessary.
I will try a slightly different but similar strategy.
I'll probably end up reverting this before the bigger release
since it is not part of the official public API. However,
it is useful to be able to compare the performance between
functional components and classes.
Updates are scheduled by setting a work priority on the fiber and bubbling it to
the root. Because the instance does not know which tree is current at any given
time, the update is scheduled on both fiber alternates.

Need to add more unit tests to cover edge cases.
Changes the type of pendingState to be a linked list of partial
state objects.
Add support for setState((state, props) => newState).

Rename pendingState to stateQueue.
Rather than bubble up both trees, bubble up once and assign to the
alternate at each level.

Extract logic for adding to the queue to the StateQueue module.
Use a union type for the head of StateQueue.
Move ReactInstanceMap to src/renderers/shared/shared to indicate that
this logic is shared across implementations.
Also cleans up some types.
Callbacks are stored on the same queue as updates. They care called
during the commit phase, after the updates have been flushed.

Because the update queue is cleared during the completion phase (before
commit), a new field has been added to fiber called callbackList. The
queue is transferred from updateQueue to callbackList during completion.
During commit, the list is reset.

Need a test to confirm that callbacks are not lost if an update is
preempted.
Adds a field to UpdateQueue that indicates whether an update should
replace the previous state completely.
Adds a field to the update queue that causes shouldComponentUpdate to
be skipped.
We should be able to abort an update without any side-effects to the
current tree. This fixes a few cases where that was broken.

The callback list should only ever be set on the workInProgress.
There's no reason to add it to the current tree because they're not
needed after they are called during the commit phase.

Also found a bug where the memoizedProps were set to null in the
case of an update, because the pendingProps were null. Fixed by
transfering the props from the instance, like we were already doing
with state.

Added a test to ensure that setState can be called inside a
callback.
This is unfortunate since we agreed on using the `null | Fiber`
convention instead of `?Fiber` but haven't upgraded yet and this
is the pattern I've been using everywhere else so far.
@sebmarkbage
Copy link
Collaborator

Currently there is a single priority field `pendingWorkPriority` that
represents the priority of the entire subtree. The scheduler does a
breadth-first search to find the next unit of work with a matching
priority level. If a subtree's priority does not match, that entire
subtree is skipped. That means when an update is scheduled deep in the
tree (`setState`), its priority must be bubbled to the top of the tree.
The problem is that this causes the all the parent priorities to be
overridden — there's no way to schedule a high priority update inside
a low priority tree without increasing the priority of the entire tree.

To address this, this PR introduces a separate priority field called
`pendingUpdatePriority` (not 100% sold on the name). The new field
represents the priority of the pending props and state, which may be
lower than the priority of the entire tree. Now, when the scheduler is
searching for work to perform, it only matches if the `
pendingUpdatePriority` matches. It continues to use the
`pendingWorkPriority` as a way to ignore subtrees if they do not match.
(To elaborate further: because the work priority is the highest priority
 of any node in the entire tree, if it does not match, the tree can
be skipped.)
@acdlite acdlite force-pushed the fiberupdatepriority branch from 7036ef8 to c948425 Compare September 3, 2016 22:50
@ghost ghost added the CLA Signed label Sep 3, 2016
@sebmarkbage
Copy link
Collaborator

In the case of down-prioritization, we probably want to down-prioritize all setState. Those wouldn't be increasing the priority level, instead they would decrease the priority of the setState to the same as the rest of the down-prioritized tree. The only down-priority case that I have in mind right now at least, is offscreen content where state updates within it should also be down-prioritized.

The only time you end up with pendingProps hanging in your "current" tree is if something was down-prioritized. E.g. with the hidden field. However, in that case there is no way for it to become up-prioritized if that take precedence.

Therefore, I don't think we actually need a separate field for this other than for state updates. I.e. we could move this field into the updateQueue.

Let me know what you think.

@sebmarkbage
Copy link
Collaborator

You can also end up with pendingProps in progressed work in the work-in-progress tree I guess. Not sure how that affects the setState case.

@gaearon
Copy link
Collaborator

gaearon commented Oct 26, 2016

I'll close since this is outdated. I also think it was covered by #8010?

@gaearon gaearon closed this Oct 26, 2016
@acdlite
Copy link
Collaborator Author

acdlite commented Oct 26, 2016

Yeah way outdated

@sebmarkbage
Copy link
Collaborator

It isn't not covered yet though. In fact the solution to this problem is blocking me from landing the demo properly. You can't really do as interesting scheduling examples without the separate priority field.

So we should fix something like it but by putting the field on the update queue.

@acdlite
Copy link
Collaborator Author

acdlite commented Oct 26, 2016

@sebmarkbage Is this tracked on the umbrella issue? I can pick this back up again, unless you'd rather do it

@sebmarkbage
Copy link
Collaborator

@acdlite It is not tracked but we probably should. It'd be great if you can pick it back up so we can get the demo with hover effects running smoothly!

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

Successfully merging this pull request may close these issues.

4 participants