Skip to content

[Umbrella] Async rendering #8830

Closed
Closed
@acdlite

Description

@acdlite

Async rendering is incomplete. There are bugs with the existing implementation and crucial features that are missing.

Specifically, the bugs are related to resuming work after it has been interupted.

#9695 was an effort to clean up the bugs without fundamentally changing the underlying model. In the course of working on that branch, we've decided that the underlying model is inherently flawed and needs to change. The tricky case is when low priority work is interrupted by a higher priority update. We want to be able to reconcile again at the higher priority without losing the low priority children, so that we can resume them later. There's no way to do this with the existing model.

So we're going to scrap the model and start again.

Scrap existing "progressed work" implementation and its bugs

This will give us a better foundation upon which to build the new model. It should also fix bugs in the triangle demo, although starvation will clearly be worse. We should aim for correctness before comprehensiveness.

  • Add fuzz tester to protect against regressions. [@acdlite] (Fuzz tester that simulates Sierpinski Triangle demo #9952)
    • Difficult to impossible to write unit tests that provide sufficient coverage, especially ones that are resilient to implementation changes. A fuzz tester provides more safety.
    • Should not make assertions on how work is reused, only on consistency.
  • Remove all existing code related to "progressed" or "forked" work. [@acdlite] (Remove progressed work #10008)
  • Split pendingWorkPriority from the update priority so that it represents the priority of the subtree, but not the fiber it belongs to. This lets us know whether the children have remaining work.

Expiration times

The next step will be to implement expiration times so that low priority work doesn't. It's possible that expiration times alone are sufficient to generate real product wins, even without the ability to resume interrupted work.

  • Implement expiration times [@acdlite] (Expiration times #10426)
    • Updates to the same fiber at the same priority level should coalesce (commit all at once).
    • "Bucket" updates by rounding expiration times. This may be sufficient the solve to coalescing problem.

Async top-level API

Keep track of next unit of work per root

  • Is this possible? What about context?
  • scheduleUpdate (setState) currently does not always reach the root, so when we receive an update on a fiber, we don't necessarily know which tree the fiber belongs to.

Flush interaction work synchronously

Expiration boundaries and blockers (shouldComponentBlock)

Still figuring out the details for how this will work

  • Components can block rendering using shouldComponentBlock(props, state)
    • When a component blocks, React searches for the nearest expiration boundary. Has similar semantics to error boundaries.
    • If/when the update expires, switches to a renderExpired tree instead.
    • Unblock using this.ping (actual name TK).

Resuming interrupted work

Then we can move onto to addressing the problem of resuming interrupted work.

  • Implement resuming in the basic case, where the fiber was not touched since the last time we worked on it.
    • progressedPriority should represent the priority that the parent last reconciled at.
  • child is a set of all children, present and future (and maybe some in the past)?
    • Still figuring out the details.
    • How will re-orders work?

Other items

  • Resume mount bug: null is passed as props to constructor [@acdlite] (Fix bug where null props is passed to constructor when resuming mount #9576)
  • Resume mount bug: creating a new instance on resume causes refs (or callbacks in user-space) to close over the wrong instance. Fix by reusing the original instance. [@acdlite] (Don't recreate instance when resuming a class component's initial mount #9608)
  • Image load event may fire before it's been mounted into the DOM. Figure out a way to defer this event (and other applicable ones) until after mount.
  • Ensure error boundaries work in incremental mode, e.g. in a hidden subtree.
  • Don't commit during requestIdleCallback. Wait until the next animation frame, then flush animation work using the last completed priority level. If the work overlaps, we may be able to reuse it. (This item may need to wait until we switch to expiration times.)
  • Ensure work expires even if there are no rIC or rAF events.
  • Solve stale event listeners: When a component receives an interaction event, flush updates in parents and "simulate" a render to recreate event handler before calling it.
  • Defer event dispatching in a proper and fast way. See Ignore events on not-yet-mounted fibers #9742 and Hydration of previously rendered server markup #9580 for context.
  • Solve the case where you want to show some fallback content only if the primary content takes too long but not if it is fast. Such as rendering a spinner if an async render takes too long. What if when the general expiration time elapsed for an async tree, we started calling an alternate renderExpired tree instead? That way we can render the tree with a spinner, only if it took too long to render. This is Suspense.
  • Figure out story around unit-testing async components. We probably just want to force sync mode. What API should we provide to Enzyme/TestUtils to do this?

Metadata

Metadata

Assignees

No one assigned

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions