* Revert "Revert "[Scheduler] Profiling features (#16145)" (#16392)"
This reverts commit 4ba1412305.
* Fix copy paste mistake
* Remove init path dependency on ArrayBuffer
* Add a regression test for cancelling multiple tasks
* Prevent deopt from adding isQueued later
* Remove pop() calls that were added for profiling
* Verify that Suspend/Unsuspend events match up in tests
This currently breaks tests.
* Treat Suspend and Resume as exiting and entering work loop
Their definitions used to be more fuzzy. For example, Suspend didn't always fire on exit, and sometimes fired when we did _not_ exit (such as at task enqueue).
I chatted to Boone, and he's saying treating Suspend and Resume as strictly exiting and entering the loop is fine for their use case.
* Revert "Prevent deopt from adding isQueued later"
This reverts commit 9c30b0b695.
Unnecessary because GCC
* Start counter with 1
* Group exports into unstable_Profiling namespace
* No catch in PROD codepath
* No label TODO
* No null checks
* Refactor a bit to use less property access
* Add test for invoking an event before mount
* Add Hydration effect tag
This is equivalent to a "Placement" effect in that it's a new insertion
to the tree but it doesn't need an actual mutation.
It is only used to determine if a subtree has actually mounted yet.
* Use the Hydration flag for Roots
Previous roots had a Placement flag on them as a hack for this case but
since we have a special flag for it now, we can just use that.
* Add Flare test
* [Scheduler] Mark user-timing events
Marks when Scheduler starts and stops running a task. Also marks when
a task is initially scheduled, and when Scheduler is waiting for a
callback, which can't be inferred from a sample-based JavaScript CPU
profile alone.
The plan is to use the user-timing events to build a Scheduler profiler
that shows how the lifetime of tasks interact with each other and
with unscheduled main thread work.
The test suite works by printing an text representation of a
Scheduler flamegraph.
* Expose shared array buffer with profiling info
Array contains
- the priority Scheduler is currently running
- the size of the queue
- the id of the currently running task
* Replace user-timing calls with event log
Events are written to an array buffer using a custom instruction format.
For now, this is only meant to be used during page start up, before the
profiler worker has a chance to start up. Once the worker is ready, call
`stopLoggingProfilerEvents` to return the log up to that point, then
send the array buffer to the worker.
Then switch to the sampling based approach.
* Record the current run ID
Each synchronous block of Scheduler work is given a unique run ID. This
is different than a task ID because a single task will have more than
one run if it yields with a continuation.
Provides partial support for React.lazy() components from the existing PartialRenderer server-side renderer.
Lazy components which are already resolved (or rejected), perhaps with something like `react-ssr-prepass`, can be continued into synchronously. If they have not yet been initialized, they'll be initialized before checking, opening the possibility to exploit this capability with a babel transform. If they're pending (which will typically be the case for a just initialized async ctor) then the existing invariant continues to be thrown.
* Test that we can suspend updates while waiting to hydrate
* Attempt hydration at a higher pri first if props/context changes
* Retrying a dehydrated boundary pings at the earliest forced time
This might quickly become an already expired time.
* Mark the render as delayed if we have to retry
This allows the suspense config to kick in and we can wait for much longer
before we're forced to give up on hydrating.
If we get an insertion after a boundary, that has not yet been hydrated,
we take our best guess at which state the HTML is showing.
isSuspenseInstancePending means that we're still waiting for more server
HTML before we can hydrate. This should mean that we're showing the
fallback state.
isSuspenseInstanceFallback means that we want to client render something.
That most likely means that the server was unable to render and is
displaying a fallback state in this slot.
Adds tests to ensure that dehydrated components don't consider the force
flag.
* Move dehydrated to be child of regular SuspenseComponent
We now store the comment node on SuspenseState instead and that indicates
that this SuspenseComponent is still dehydrated.
We also store a child but that is only used to represent the DOM node for
deletions and getNextHostSibling.
* Move logic from DehydratedSuspenseComponent to SuspenseComponent
Forked based on SuspenseState.dehydrated instead.
* Retry logic for dehydrated boundary
We can now simplify the logic for retrying dehydrated boundaries without
hydrating. This is becomes simply a reconciliation against the dehydrated
fragment which gets deleted, and the new children gets inserted.
* Remove dehydrated from throw
Instead we use the regular Suspense path. To save code, we attach retry
listeners in the commit phase even though technically we don't have to.
* Pop to nearest Suspense
I think this is right...?
* Popping hydration state should skip past the dehydrated instance
* Split mount from update and special case suspended second pass
The DidCapture flag isn't used consistently in the same way. We need
further refactor for this.
* Reorganize update path
If we remove the dehydration status in the first pass and then do a second
pass because we suspended, then we need to continue as if it didn't
previously suspend. Since there is no fragment child etc.
However, we must readd the deletion.
* Schedule context work on the boundary and not the child
* Warn for Suspense hydration in legacy mode
It does a two pass render that client renders the content.
* Rename DehydratedSuspenseComponent -> DehydratedFragment
This now doesn't represent a suspense boundary itself. Its parent does.
This Fiber represents the fragment around the dehydrated content.
* Refactor returns
Avoids the temporary mutable variables. I kept losing track of them.
* Add a comment explaining the type.
Placing it in the type since that's the central point as opposed to spread
out.
This is a bad bug. It means that we sometimes inherit
didReceiveUpdate from a previous component's begin.
Effectively this only means that we're overrendering in some cases.
We should refactor to get rid of this as a global flag.
Currently this is getting marked as Never which is the normal continuation
for a dehydrated boundary, but if it is client-rendered it has a higher
priority. That causes us to drop the interaction tracing for that render.
This colocates the marking where we actually set the expiration time.
These were discovered by @SimenB in https://github.com/facebook/react/pull/16332. We weren't making actual assertions on some values. This PR makes the assertions, and fixes the tests.
When React detects a deprectated/unsafe lifecycle method, the warning points to a page with more details on the why/what of the warning. However, the actual link (https://fb.me/react-async-component-lifecycle-hooks) uses the phrase "lifecycle-hooks" which is confusing since it doesn't have anything to do with hooks. This PR changes the link to something less confusing - https://fb.me/react-unsafe-component-lifecycles.
We might reenter a hydration state, when attempting to hydrate a boundary.
We need to ensure that we reset it to not hydrating once we exit it.
Otherwise the next sibling will still be in hydration mode.
When React schedules a rendering task, it passes a `timeout` option
based on its expiration time. This is intended to avoid starvation
by other React updates. However, it also affects the relative priority
of React tasks and other Scheduler tasks at the same level, like
data processing.
This adds a feature flag to disable passing a `timeout` option to
Scheduler. React tasks will always append themselves to the end of
the queue, without jumping ahead of already scheduled tasks.
This does not affect the order in which React updates within a single
root are processed, but it could affect updates across multiple roots.
This also doesn't remove the expiration from Scheduler. It only means
that React tasks are not given special treatment.
Previously, the suspense priority warning was fired even if the Root wasn't suspended. Changed the warning to fire only when the root is suspended.
Also refactored the suspense priority warning so it's easier to read.
The clean-up function of a passive effect (`useEffect`) usually fires
in a post-commit task, after the browser has painted. However, there is
an exception when the component (or its parent) is deleted from the
tree. In that case, we fire the clean-up function during the
synchronous commit phase, the same phase we use for layout effects.
This is a concession to implementation complexity. Calling it in the
passive effect phase would require either traversing the children of the
deleted fiber again, or including unmount effects as part of the fiber
effect list.
Because the functions are called during the sync phase in this case,
the Scheduler priority is Immediate (the one used for layout) instead
of Normal. We may want to reconsider this trade off later.
If there are adjacent updates we bail out of rendering the suspense list
at all but we may still complete the node. We need to reset the render
state in that case.
I restructured so that this is in the same code path so we don't forget it
in the future.
* Add a feature flag to disable legacy context
* Address review
- invariant -> warning
- Make this.context and context argument actually undefined
* Increase test coverage for lifecycles
* Also disable it on the server is flag is on
* Make this.context {} when disabled, but function context is undefined
* Move checks inside
* [act] Wrap IsThisRendererActing in DEV check
So that it doesn't leak into the production bundle. Follow-up to #16240.
* Disable Suspense fallback test in prod
* Bugfix: Priority when effects are flushed early
The priority of passive effects is supposed to be the same as the
priority of the render. This fixes a bug where the priority is sometimes
wrong if the effects are flushed early.
But the priority should really never be higher than Normal Priority.
I'll change that in the next commit.
* Effects never have higher than normal priority
Effects currently have the same priority as the render that spawns them.
This changes the behavior so that effects always have normal priority,
or lower if the render priority is lower (e.g. offscreen prerendering).
The implementation is a bit awkward because of the way `renderRoot`,
`commitRoot`, and `flushPassiveEffects` are split. This is a known
factoring problem that I'm planning to address once 16.9 is released.
In this PR, for tests (specifically, code inside an `act()` scope), we immediately trigger work that would have otherwise required a timeout. This makes it simpler to tests loading/spinner states, and makes tests resilient to changes in React.
For some of our tests(specifically, ReactSuspenseWithNoopRenderer-test.internal), we _don't_ want fallbacks to immediately trigger, because we're testing intermediate states and such. Added a feature flag `flushSuspenseFallbacksInTests` to disable this behaviour on a per case basis.
Concurrent/Batched mode tests should always be run with a mocked scheduler (v17 or not). This PR adds a warning for the same. I'll put up a separate PR to the docs with a page detailing how to mock the scheduler.