-
Notifications
You must be signed in to change notification settings - Fork 47k
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
Invalid actualDuration+treeBaseDuration for hidden+suspended trees #14065
Invalid actualDuration+treeBaseDuration for hidden+suspended trees #14065
Conversation
Details of bundled changes.Comparing: 5afa1c4...f64f3e2 react-dom
react-art
react-test-renderer
react-reconciler
react-native-renderer
scheduler
Generated by 🚫 dangerJS |
95303e1
to
2a955dd
Compare
@@ -753,7 +753,7 @@ function mountLazyComponent( | |||
) { | |||
if (_current !== null) { | |||
// An lazy component only mounts if it suspended inside a non- | |||
// concurrent tree, in an inconsistent state. We want to tree it like |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
🌳
I've updated my new test to cover Are there any special concurrent rendering cases that I'm overlooking? Initially, it looks like the It looks like I'm going to add a hacky fix for this as a conversation start, @acdlite. We can talk more about a better fix when you get a chance to look at this PR. 😄 |
d9ee3db
to
9af69ea
Compare
// Otherwise the profiler's onRender metrics will be off, | ||
// and the DevTools Profiler flamegraph will visually break as well. | ||
primaryChildFragment.treeBaseDuration = | ||
currentPrimaryChild.treeBaseDuration; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Need to transfer from the siblings, too.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Good catch. Fixed and updated tests to cover this via 7be6879
if (enableProfilerTimer && workInProgress.mode & ProfileMode) { | ||
// Fiber treeBaseDuration must be at least as large as the sum of children treeBaseDurations. | ||
// Otherwise the profiler's onRender metrics will be off, | ||
// and the DevTools Profiler flamegraph will visually break as well. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Could you rewrite this comment in terms of what it means semantically instead of what will break if it goes wrong?
"treeBaseDuration is the sum of all the children durations" would be sufficient
if (enableProfilerTimer && nextUnitOfWork.mode & ProfileMode) { | ||
// Record the time spent rendering before an error was thrown. | ||
// This avoids inaccurate Profiler durations in the case of a suspended render. | ||
stopProfilerTimerIfRunningAndRecordDelta(nextUnitOfWork, true); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This gets called again in completeUnitOfWork
, with no work in between. Seems wrong?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I don't think so, because completeUnitOfWork
also calls startProfilerTimer
before it starts working on the complete phase.
This clears out the previous timer, erasing any time spent in the "begin" phase. That's what we're recording here.
|
||
// HACK Also propagate actualDuration for the time spent in the fiber that errored. | ||
// This avoids inaccurate Profiler durations in the case of a suspended render. | ||
// This happens automatically for sync renders, because of resetChildExpirationTime. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Suggested alternative: bubble this in completeUnitOfWork
, where resetChildExpirationTime
would normally go for a completed fiber.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
We already have a similar bubbling there to ensure that actualDuration
bubbles all the way up to the Suspense
component (or error boundary).
I think it's tricky to propagate this specific value (from the fiber that threw) in completeUnitOfWork
without counting it too many times (since we unwind and process ancestors between the fiber that throws and either Suspense
or an error boundary).
We know we've reached the ancestor boundary when next != null
but...I'm not sure of a heuristic to only update for the fiber that threw. Conceptually, it feels like the right place to do this is where we catch.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Scratch that, I misunderstood your initial suggestion. You're right!
85c5831
to
7be6879
Compare
37ab9da
to
b51501b
Compare
Rebased to account for improvements made in e9a2ec9 😄 |
@@ -1246,6 +1251,18 @@ function updateSuspenseComponent( | |||
NoWork, | |||
null, | |||
); | |||
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Suggested comment:
Because
primaryChildFragment
is a new fiber that we're inserting as the parent of a new tree, we need to set itstreeBaseDuration
.
2b52374
to
103d9d2
Compare
Initial suspended tree is no longer sync-committed before fallback UI, so tests needed to remove this assumption.
103d9d2
to
f64f3e2
Compare
Rebasing one last time |
…acebook#14065) * Fixed `treeBaseDuration` by propagating its value from the suspended tree to the Fragment React temporarily wraps around it when showing the fallback UI. * Fixed `actualDuration` by recording elapsed profiler time in the event of an error. * Fixed `actualDuration` in concurrent mode by propagating the time spent rendering the suspending component to its parent. Also updated ReactSuspensePlaceholder-test.internal to cover these new cases.
…acebook#14065) * Fixed `treeBaseDuration` by propagating its value from the suspended tree to the Fragment React temporarily wraps around it when showing the fallback UI. * Fixed `actualDuration` by recording elapsed profiler time in the event of an error. * Fixed `actualDuration` in concurrent mode by propagating the time spent rendering the suspending component to its parent. Also updated ReactSuspensePlaceholder-test.internal to cover these new cases.
While testing some of our new APIs in DevTools, I noticed that the profiler (both the API and the DevTools visual profiler) reported invalid
actualDuration
andtreeBaseDuration
values when suspending.The
treeBaseDuration
was incorrect for the fallback commit. It only included the fallback subtree duration, when it should also include the time spent rendering the hidden subtree.The
actualDuration
value was incorrect for both the fallback commit and the final, resolved commit. It didn't include time spent rendering the component that actually read from the cache (the one that threw).treeBaseDuration
by propagating its value from the suspended tree to theFragment
React temporarily wraps around it when showing the fallback UI.actualDuration
by recording elapsed profiler time in the event of an error.actualDuration
in concurrent mode by propagating the time spent rendering the suspending component to its parent.Also updated
ReactSuspensePlaceholder-test.internal
to cover these new cases.