@samitouri / QOS-React / commits / 05f3ecc3ea

Performance tool: Warn when interrupting an in-progress tree (#11480)

* Performance tool: Warn when interrupting an in-progress tree * Include the name of the component that caused the interruption

Andrew Clark committed Nov 7, 2017 at 16:52 UTC 05f3ecc3eae7a6c10109f48548c001c28f4cb480
4 files changed +64 -15
packages/react-reconciler/src/ReactDebugFiberPerf.js
+14 -5
@@ -318,8 +318,9 @@ export function stopPhaseTimer(): void {
318 }
319 }
320
321 -export function startWorkLoopTimer(): void {
321 +export function startWorkLoopTimer(nextUnitOfWork: Fiber | null): void {
322 if (enableUserTimingAPI) {
323 + currentFiber = nextUnitOfWork;
324 if (!supportsUserTiming) {
325 return;
326 }
@@ -332,14 +333,22 @@ export function startWorkLoopTimer(): void {
333 }
334 }
335
335 -export function stopWorkLoopTimer(): void {
336 +export function stopWorkLoopTimer(interruptedBy: Fiber | null): void {
337 if (enableUserTimingAPI) {
338 if (!supportsUserTiming) {
339 return;
340 }
340 - const warning = commitCountInCurrentWorkLoop > 1
341 - ? 'There were cascading updates'
342 - : null;
341 + let warning = null;
342 + if (interruptedBy !== null) {
343 + if (interruptedBy.tag === HostRoot) {
344 + warning = 'A top-level update interrupted the previous render';
345 + } else {
346 + const componentName = getComponentName(interruptedBy) || 'Unknown';
347 + warning = `An update to ${componentName} interrupted the previous render`;
348 + }
349 + } else if (commitCountInCurrentWorkLoop > 1) {
350 + warning = 'There were cascading updates';
351 + }
352 commitCountInCurrentWorkLoop = 0;
353 // Pause any measurements until the next loop.
354 pauseTimers();
packages/react-reconciler/src/ReactFiberScheduler.js
+13 -6
@@ -216,6 +216,9 @@ export default function<T, P, I, TI, PI, C, CC, CX, PL>(
216 let isCommitting: boolean = false;
217 let isUnmounting: boolean = false;
218
219 + // Used for performance tracking.
220 + let interruptedBy: Fiber | null = null;
221 +
222 function resetContextStack() {
223 // Reset the stack
224 reset();
@@ -752,8 +755,6 @@ export default function<T, P, I, TI, PI, C, CC, CX, PL>(
755 root: FiberRoot,
756 expirationTime: ExpirationTime,
757 ): Fiber | null {
755 - startWorkLoopTimer();
756 -
758 invariant(
759 !isWorking,
760 'renderRoot was called recursively. This error is likely caused ' +
@@ -772,7 +773,7 @@ export default function<T, P, I, TI, PI, C, CC, CX, PL>(
773 expirationTime !== nextRenderExpirationTime ||
774 nextUnitOfWork === null
775 ) {
775 - // This is a restart. Reset the stack.
776 + // Reset the stack and start working from the root.
777 resetContextStack();
778 nextRoot = root;
779 nextRenderExpirationTime = expirationTime;
@@ -783,6 +784,8 @@ export default function<T, P, I, TI, PI, C, CC, CX, PL>(
784 );
785 }
786
787 + startWorkLoopTimer(nextUnitOfWork);
788 +
789 let didError = false;
790 let error = null;
791 if (__DEV__) {
@@ -865,12 +868,12 @@ export default function<T, P, I, TI, PI, C, CC, CX, PL>(
868 const uncaughtError = firstUncaughtError;
869
870 // We're done performing work. Time to clean up.
871 + stopWorkLoopTimer(interruptedBy);
872 + interruptedBy = null;
873 isWorking = false;
874 didFatal = false;
875 firstUncaughtError = null;
876
872 - stopWorkLoopTimer();
873 -
877 if (uncaughtError !== null) {
878 onUncaughtError(uncaughtError);
879 }
@@ -1198,7 +1201,11 @@ export default function<T, P, I, TI, PI, C, CC, CX, PL>(
1201 root === nextRoot &&
1202 expirationTime <= nextRenderExpirationTime
1203 ) {
1201 - // This is an interruption. Restart the root from the top.
1204 + // Restart the root from the top.
1205 + if (nextUnitOfWork !== null) {
1206 + // This is an interruption. (Used for performance tracking.)
1207 + interruptedBy = fiber;
1208 + }
1209 nextRoot = null;
1210 nextUnitOfWork = null;
1211 nextRenderExpirationTime = NoWork;
packages/react-reconciler/src/__tests__/ReactIncrementalPerf-test.js
+14
@@ -498,4 +498,18 @@ describe('ReactDebugFiberPerf', () => {
498 });
499 expect(getFlameChart()).toMatchSnapshot();
500 });
501 +
502 + it('warns if an in-progress update is interrupted', () => {
503 + function Foo() {
504 + return <span />;
505 + }
506 +
507 + ReactNoop.render(<Foo />);
508 + ReactNoop.flushUnitsOfWork(2);
509 + ReactNoop.flushSync(() => {
510 + ReactNoop.render(<Foo />);
511 + });
512 + ReactNoop.flush();
513 + expect(getFlameChart()).toMatchSnapshot();
514 + });
515 });
packages/react-reconciler/src/__tests__/__snapshots__/ReactIncrementalPerf-test.js.snap
+23 -4
@@ -243,6 +243,17 @@ exports[`ReactDebugFiberPerf skips parents during setState 1`] = `
243 "
244 `;
245
246 +exports[`ReactDebugFiberPerf supports portals 1`] = `
247 +"⚛ (React Tree Reconciliation)
248 + ⚛ Parent [mount]
249 + ⚛ Child [mount]
250 +
251 +⚛ (Committing Changes)
252 + ⚛ (Committing Host Effects: 2 Total)
253 + ⚛ (Calling Lifecycle Methods: 0 Total)
254 +"
255 +`;
256 +
257 exports[`ReactDebugFiberPerf supports returns 1`] = `
258 "⚛ (React Tree Reconciliation)
259 ⚛ App [mount]
@@ -260,14 +271,22 @@ exports[`ReactDebugFiberPerf supports returns 1`] = `
271 "
272 `;
273
263 -exports[`ReactDebugFiberPerf supports portals 1`] = `
274 +exports[`ReactDebugFiberPerf warns if an in-progress update is interrupted 1`] = `
275 "⚛ (React Tree Reconciliation)
265 - ⚛ Parent [mount]
266 - ⚛ Child [mount]
276 + ⚛ Foo [mount]
277 +
278 +⛔ (React Tree Reconciliation) Warning: A top-level update interrupted the previous render
279 + ⚛ Foo [mount]
280
281 ⚛ (Committing Changes)
269 - ⚛ (Committing Host Effects: 2 Total)
282 + ⚛ (Committing Host Effects: 1 Total)
283 ⚛ (Calling Lifecycle Methods: 0 Total)
284 +
285 +⚛ (React Tree Reconciliation)
286 +
287 +⚛ (Committing Changes)
288 + ⚛ (Committing Host Effects: 1 Total)
289 + ⚛ (Calling Lifecycle Methods: 1 Total)
290 "
291 `;
292