From b47bf240d656106b8066487aeb0f34e5a5cf4216 Mon Sep 17 00:00:00 2001 From: Andrew Clark Date: Thu, 9 Nov 2017 17:35:03 +0000 Subject: [PATCH 1/3] Measure time between scheduling an async callback and flushing it Helps detect starvation issues. --- .../src/ReactDebugFiberPerf.js | 28 ++++- .../src/ReactFiberScheduler.js | 17 ++- .../__tests__/ReactIncrementalPerf-test.js | 11 ++ .../ReactIncrementalPerf-test.js.snap | 114 +++++++++++++----- 4 files changed, 137 insertions(+), 33 deletions(-) diff --git a/packages/react-reconciler/src/ReactDebugFiberPerf.js b/packages/react-reconciler/src/ReactDebugFiberPerf.js index b4717fc8a9b..b81608e4b83 100644 --- a/packages/react-reconciler/src/ReactDebugFiberPerf.js +++ b/packages/react-reconciler/src/ReactDebugFiberPerf.js @@ -58,6 +58,7 @@ let hasScheduledUpdateInCurrentCommit: boolean = false; let hasScheduledUpdateInCurrentPhase: boolean = false; let commitCountInCurrentWorkLoop: number = 0; let effectCountInCurrentCommit: number = 0; +let isWaitingForCallback: boolean = false; // During commits, we only show a measurement once per method name // to avoid stretch the commit phase with measurement overhead. const labelsInCurrentCommit: Set = new Set(); @@ -231,6 +232,29 @@ export function recordScheduleUpdate(): void { } } +export function startRequestCallbackTimer(): void { + if (enableUserTimingAPI) { + if (supportsUserTiming && !isWaitingForCallback) { + isWaitingForCallback = true; + beginMark('(Waiting for async callback...)'); + } + } +} + +export function stopRequestCallbackTimer(didExpire: boolean): void { + if (enableUserTimingAPI) { + if (supportsUserTiming) { + isWaitingForCallback = false; + const warning = didExpire ? 'Async work expired' : null; + endMark( + '(Waiting for async callback...)', + '(Waiting for async callback...)', + warning, + ); + } + } +} + export function startWorkTimer(fiber: Fiber): void { if (enableUserTimingAPI) { if (!supportsUserTiming || shouldIgnoreFiber(fiber)) { @@ -344,9 +368,7 @@ export function stopWorkLoopTimer(interruptedBy: Fiber | null): void { warning = 'A top-level update interrupted the previous render'; } else { const componentName = getComponentName(interruptedBy) || 'Unknown'; - warning = `An update to ${ - componentName - } interrupted the previous render`; + warning = `An update to ${componentName} interrupted the previous render`; } } else if (commitCountInCurrentWorkLoop > 1) { warning = 'There were cascading updates'; diff --git a/packages/react-reconciler/src/ReactFiberScheduler.js b/packages/react-reconciler/src/ReactFiberScheduler.js index 95cf4c276c0..80981b6af63 100644 --- a/packages/react-reconciler/src/ReactFiberScheduler.js +++ b/packages/react-reconciler/src/ReactFiberScheduler.js @@ -13,7 +13,9 @@ import type {FiberRoot} from './ReactFiberRoot'; import type {HydrationContext} from './ReactFiberHydrationContext'; import type {ExpirationTime} from './ReactFiberExpirationTime'; -import {getStackAddendumByWorkInProgressFiber} from 'shared/ReactFiberComponentTreeHook'; +import { + getStackAddendumByWorkInProgressFiber, +} from 'shared/ReactFiberComponentTreeHook'; import ReactErrorUtils from 'shared/ReactErrorUtils'; import {ReactCurrentOwner} from 'shared/ReactGlobalSharedState'; import { @@ -33,6 +35,7 @@ import { HostPortal, ClassComponent, } from 'shared/ReactTypeOfWork'; +import {enableUserTimingAPI} from 'shared/ReactFeatureFlags'; import getComponentName from 'shared/getComponentName'; import invariant from 'fbjs/lib/invariant'; import warning from 'fbjs/lib/warning'; @@ -47,6 +50,8 @@ import ReactDebugCurrentFiber from './ReactDebugCurrentFiber'; import { recordEffect, recordScheduleUpdate, + startRequestCallbackTimer, + stopRequestCallbackTimer, startWorkTimer, stopWorkTimer, stopFailedWorkTimer, @@ -1337,6 +1342,7 @@ export default function( performWork(Sync, null); } else if (!isCallbackScheduled) { isCallbackScheduled = true; + startRequestCallbackTimer(); scheduleDeferredCallback(performAsyncWork); } } @@ -1425,8 +1431,14 @@ export default function( deadline = dl; // Keep working on roots until there's no more work, or until the we reach - // the deadlne. + // the deadline. findHighestPriorityRoot(); + + if (enableUserTimingAPI && deadline !== null) { + const didExpire = nextFlushedExpirationTime < recalculateCurrentTime(); + stopRequestCallbackTimer(didExpire); + } + while ( nextFlushedRoot !== null && nextFlushedExpirationTime !== NoWork && @@ -1449,6 +1461,7 @@ export default function( // If there's work left over, schedule a new callback. if (nextFlushedRoot !== null && !isCallbackScheduled) { isCallbackScheduled = true; + startRequestCallbackTimer(); scheduleDeferredCallback(performAsyncWork); } diff --git a/packages/react-reconciler/src/__tests__/ReactIncrementalPerf-test.js b/packages/react-reconciler/src/__tests__/ReactIncrementalPerf-test.js index 0ea69b10ad2..604e209eb7f 100644 --- a/packages/react-reconciler/src/__tests__/ReactIncrementalPerf-test.js +++ b/packages/react-reconciler/src/__tests__/ReactIncrementalPerf-test.js @@ -546,4 +546,15 @@ describe('ReactDebugFiberPerf', () => { ReactNoop.flush(); expect(getFlameChart()).toMatchSnapshot(); }); + + it('warns if async work expires (starvation)', () => { + function Foo() { + return ; + } + + ReactNoop.render(); + ReactNoop.expire(5000); + ReactNoop.flush(); + expect(getFlameChart()).toMatchSnapshot(); + }); }); diff --git a/packages/react-reconciler/src/__tests__/__snapshots__/ReactIncrementalPerf-test.js.snap b/packages/react-reconciler/src/__tests__/__snapshots__/ReactIncrementalPerf-test.js.snap index 0bd35bd7012..57232fd0bb0 100644 --- a/packages/react-reconciler/src/__tests__/__snapshots__/ReactIncrementalPerf-test.js.snap +++ b/packages/react-reconciler/src/__tests__/__snapshots__/ReactIncrementalPerf-test.js.snap @@ -1,7 +1,9 @@ // Jest Snapshot v1, https://goo.gl/fbAQLP exports[`ReactDebugFiberPerf captures all lifecycles 1`] = ` -"// Mount +"⚛ (Waiting for async callback...) + // Mount + ⚛ (React Tree Reconciliation) ⚛ AllLifecycles [mount] ⚛ AllLifecycles.componentWillMount @@ -12,7 +14,9 @@ exports[`ReactDebugFiberPerf captures all lifecycles 1`] = ` ⚛ (Calling Lifecycle Methods: 1 Total) ⚛ AllLifecycles.componentDidMount -// Update +⚛ (Waiting for async callback...) + // Update + ⚛ (React Tree Reconciliation) ⚛ AllLifecycles [update] ⚛ AllLifecycles.componentWillReceiveProps @@ -25,7 +29,9 @@ exports[`ReactDebugFiberPerf captures all lifecycles 1`] = ` ⚛ (Calling Lifecycle Methods: 2 Total) ⚛ AllLifecycles.componentDidUpdate -// Unmount +⚛ (Waiting for async callback...) + // Unmount + ⚛ (React Tree Reconciliation) ⚛ (Committing Changes) @@ -36,7 +42,9 @@ exports[`ReactDebugFiberPerf captures all lifecycles 1`] = ` `; exports[`ReactDebugFiberPerf deduplicates lifecycle names during commit to reduce overhead 1`] = ` -"// The commit phase should mention A and B just once +"⚛ (Waiting for async callback...) + // The commit phase should mention A and B just once + ⚛ (React Tree Reconciliation) ⚛ Parent [update] ⚛ A [update] @@ -50,8 +58,10 @@ exports[`ReactDebugFiberPerf deduplicates lifecycle names during commit to reduc ⚛ A.componentDidUpdate ⚛ B.componentDidUpdate -// Because of deduplication, we don't know B was cascading, -// but we should still see the warning for the commit phase. +⚛ (Waiting for async callback...) + // Because of deduplication, we don't know B was cascading, + // but we should still see the warning for the commit phase. + ⚛ (React Tree Reconciliation) ⚛ Parent [update] ⚛ A [update] @@ -94,7 +104,9 @@ exports[`ReactDebugFiberPerf does not schedule an extra callback if setState is `; exports[`ReactDebugFiberPerf does not treat setState from cWM or cWRP as cascading 1`] = ` -"// Should not print a warning +"⚛ (Waiting for async callback...) + // Should not print a warning + ⚛ (React Tree Reconciliation) ⚛ Parent [mount] ⚛ NotCascading [mount] @@ -104,7 +116,9 @@ exports[`ReactDebugFiberPerf does not treat setState from cWM or cWRP as cascadi ⚛ (Committing Host Effects: 1 Total) ⚛ (Calling Lifecycle Methods: 0 Total) -// Should not print a warning +⚛ (Waiting for async callback...) + // Should not print a warning + ⚛ (React Tree Reconciliation) ⚛ Parent [update] ⚛ NotCascading [update] @@ -117,7 +131,9 @@ exports[`ReactDebugFiberPerf does not treat setState from cWM or cWRP as cascadi `; exports[`ReactDebugFiberPerf measures a simple reconciliation 1`] = ` -"// Mount +"⚛ (Waiting for async callback...) + // Mount + ⚛ (React Tree Reconciliation) ⚛ Parent [mount] ⚛ Child [mount] @@ -126,7 +142,9 @@ exports[`ReactDebugFiberPerf measures a simple reconciliation 1`] = ` ⚛ (Committing Host Effects: 1 Total) ⚛ (Calling Lifecycle Methods: 0 Total) -// Update +⚛ (Waiting for async callback...) + // Update + ⚛ (React Tree Reconciliation) ⚛ Parent [update] ⚛ Child [update] @@ -135,7 +153,9 @@ exports[`ReactDebugFiberPerf measures a simple reconciliation 1`] = ` ⚛ (Committing Host Effects: 2 Total) ⚛ (Calling Lifecycle Methods: 2 Total) -// Unmount +⚛ (Waiting for async callback...) + // Unmount + ⚛ (React Tree Reconciliation) ⚛ (Committing Changes) @@ -145,18 +165,24 @@ exports[`ReactDebugFiberPerf measures a simple reconciliation 1`] = ` `; exports[`ReactDebugFiberPerf measures deferred work in chunks 1`] = ` -"// Start mounting Parent and A +"⚛ (Waiting for async callback...) + // Start mounting Parent and A + ⚛ (React Tree Reconciliation) ⚛ Parent [mount] ⚛ A [mount] ⚛ Child [mount] -// Mount B just a little (but not enough to memoize) +⚛ (Waiting for async callback...) + // Mount B just a little (but not enough to memoize) + ⚛ (React Tree Reconciliation) ⚛ Parent [mount] ⚛ B [mount] -// Complete B and Parent +⚛ (Waiting for async callback...) + // Complete B and Parent + ⚛ (React Tree Reconciliation) ⚛ Parent [mount] ⚛ B [mount] @@ -177,7 +203,9 @@ exports[`ReactDebugFiberPerf measures deprioritized work 1`] = ` ⚛ (Committing Host Effects: 1 Total) ⚛ (Calling Lifecycle Methods: 0 Total) -// Flush the child +⚛ (Waiting for async callback...) + // Flush the child + ⚛ (React Tree Reconciliation) ⚛ Child [mount] @@ -188,7 +216,9 @@ exports[`ReactDebugFiberPerf measures deprioritized work 1`] = ` `; exports[`ReactDebugFiberPerf recovers from caught errors 1`] = ` -"// Stop on Baddie and restart from Boundary +"⚛ (Waiting for async callback...) + // Stop on Baddie and restart from Boundary + ⚛ (React Tree Reconciliation) ⚛ Parent [mount] ⛔ Boundary [mount] Warning: An error was thrown inside this error boundary @@ -211,7 +241,9 @@ exports[`ReactDebugFiberPerf recovers from caught errors 1`] = ` `; exports[`ReactDebugFiberPerf recovers from fatal errors 1`] = ` -"// Will fatal +"⚛ (Waiting for async callback...) + // Will fatal + ⚛ (React Tree Reconciliation) ⚛ Parent [mount] ⚛ Baddie [mount] @@ -220,7 +252,9 @@ exports[`ReactDebugFiberPerf recovers from fatal errors 1`] = ` ⚛ (Committing Host Effects: 1 Total) ⚛ (Calling Lifecycle Methods: 1 Total) -// Will reconcile from a clean state +⚛ (Waiting for async callback...) + // Will reconcile from a clean state + ⚛ (React Tree Reconciliation) ⚛ Parent [mount] ⚛ Child [mount] @@ -232,7 +266,9 @@ exports[`ReactDebugFiberPerf recovers from fatal errors 1`] = ` `; exports[`ReactDebugFiberPerf skips parents during setState 1`] = ` -"// Should include just A and B, no Parents +"⚛ (Waiting for async callback...) + // Should include just A and B, no Parents + ⚛ (React Tree Reconciliation) ⚛ A [update] ⚛ B [update] @@ -244,7 +280,9 @@ exports[`ReactDebugFiberPerf skips parents during setState 1`] = ` `; exports[`ReactDebugFiberPerf supports portals 1`] = ` -"⚛ (React Tree Reconciliation) +"⚛ (Waiting for async callback...) + +⚛ (React Tree Reconciliation) ⚛ Parent [mount] ⚛ Child [mount] @@ -255,7 +293,9 @@ exports[`ReactDebugFiberPerf supports portals 1`] = ` `; exports[`ReactDebugFiberPerf supports returns 1`] = ` -"⚛ (React Tree Reconciliation) +"⚛ (Waiting for async callback...) + +⚛ (React Tree Reconciliation) ⚛ App [mount] ⚛ CoParent [mount] ⚛ HandleReturns [mount] @@ -272,26 +312,42 @@ exports[`ReactDebugFiberPerf supports returns 1`] = ` `; exports[`ReactDebugFiberPerf warns if an in-progress update is interrupted 1`] = ` -"⚛ (React Tree Reconciliation) - ⚛ Foo [mount] +"⚛ (Waiting for async callback...) -⛔ (React Tree Reconciliation) Warning: A top-level update interrupted the previous render +⚛ (React Tree Reconciliation) ⚛ Foo [mount] +⚛ (Waiting for async callback...) + ⛔ (React Tree Reconciliation) Warning: A top-level update interrupted the previous render + ⚛ Foo [mount] + ⚛ (Committing Changes) + ⚛ (Committing Host Effects: 1 Total) + ⚛ (Calling Lifecycle Methods: 0 Total) + +⚛ (React Tree Reconciliation) + ⚛ (Committing Changes) ⚛ (Committing Host Effects: 1 Total) - ⚛ (Calling Lifecycle Methods: 0 Total) + ⚛ (Calling Lifecycle Methods: 1 Total) +" +`; + +exports[`ReactDebugFiberPerf warns if async work expires (starvation) 1`] = ` +"⛔ (Waiting for async callback...) Warning: Async work expired ⚛ (React Tree Reconciliation) + ⚛ Foo [mount] ⚛ (Committing Changes) ⚛ (Committing Host Effects: 1 Total) - ⚛ (Calling Lifecycle Methods: 1 Total) + ⚛ (Calling Lifecycle Methods: 0 Total) " `; exports[`ReactDebugFiberPerf warns on cascading renders from setState 1`] = ` -"// Should print a warning +"⚛ (Waiting for async callback...) + // Should print a warning + ⚛ (React Tree Reconciliation) ⚛ Parent [mount] ⚛ Cascading [mount] @@ -311,7 +367,9 @@ exports[`ReactDebugFiberPerf warns on cascading renders from setState 1`] = ` `; exports[`ReactDebugFiberPerf warns on cascading renders from top-level render 1`] = ` -"// Rendering the first root +"⚛ (Waiting for async callback...) + // Rendering the first root + ⚛ (React Tree Reconciliation) ⚛ Cascading [mount] From a4e5501fa3c0340e69785e771ca5f1312bfc9363 Mon Sep 17 00:00:00 2001 From: Andrew Clark Date: Thu, 9 Nov 2017 18:22:59 +0000 Subject: [PATCH 2/3] Debug comments should print directly above the next measure --- .../src/ReactDebugFiberPerf.js | 4 +- .../src/ReactFiberScheduler.js | 4 +- .../__tests__/ReactIncrementalPerf-test.js | 9 ++-- .../ReactIncrementalPerf-test.js.snap | 44 +++++++++---------- 4 files changed, 32 insertions(+), 29 deletions(-) diff --git a/packages/react-reconciler/src/ReactDebugFiberPerf.js b/packages/react-reconciler/src/ReactDebugFiberPerf.js index b81608e4b83..1f239f7d6a0 100644 --- a/packages/react-reconciler/src/ReactDebugFiberPerf.js +++ b/packages/react-reconciler/src/ReactDebugFiberPerf.js @@ -368,7 +368,9 @@ export function stopWorkLoopTimer(interruptedBy: Fiber | null): void { warning = 'A top-level update interrupted the previous render'; } else { const componentName = getComponentName(interruptedBy) || 'Unknown'; - warning = `An update to ${componentName} interrupted the previous render`; + warning = `An update to ${ + componentName + } interrupted the previous render`; } } else if (commitCountInCurrentWorkLoop > 1) { warning = 'There were cascading updates'; diff --git a/packages/react-reconciler/src/ReactFiberScheduler.js b/packages/react-reconciler/src/ReactFiberScheduler.js index 80981b6af63..57e6fe63b71 100644 --- a/packages/react-reconciler/src/ReactFiberScheduler.js +++ b/packages/react-reconciler/src/ReactFiberScheduler.js @@ -13,9 +13,7 @@ import type {FiberRoot} from './ReactFiberRoot'; import type {HydrationContext} from './ReactFiberHydrationContext'; import type {ExpirationTime} from './ReactFiberExpirationTime'; -import { - getStackAddendumByWorkInProgressFiber, -} from 'shared/ReactFiberComponentTreeHook'; +import {getStackAddendumByWorkInProgressFiber} from 'shared/ReactFiberComponentTreeHook'; import ReactErrorUtils from 'shared/ReactErrorUtils'; import {ReactCurrentOwner} from 'shared/ReactGlobalSharedState'; import { diff --git a/packages/react-reconciler/src/__tests__/ReactIncrementalPerf-test.js b/packages/react-reconciler/src/__tests__/ReactIncrementalPerf-test.js index 604e209eb7f..672c3d8f62a 100644 --- a/packages/react-reconciler/src/__tests__/ReactIncrementalPerf-test.js +++ b/packages/react-reconciler/src/__tests__/ReactIncrementalPerf-test.js @@ -20,6 +20,7 @@ describe('ReactDebugFiberPerf', () => { let activeMeasure; let knownMarks; let knownMeasures; + let comments; function resetFlamechart() { root = { @@ -35,12 +36,11 @@ describe('ReactDebugFiberPerf', () => { activeMeasure = root; knownMarks = new Set(); knownMeasures = new Set(); + comments = []; } function addComment(comment) { - activeMeasure.children.push( - `${' '.repeat(activeMeasure.indent + 1)}// ${comment}`, - ); + comments.push(comment); } function getFlameChart() { @@ -67,9 +67,11 @@ describe('ReactDebugFiberPerf', () => { // Will be assigned on measure() call: label: null, parent: activeMeasure, + comments, toString() { return ( [ + ...this.comments.map(c => ' '.repeat(this.indent) + '// ' + c), ' '.repeat(this.indent) + this.label, ...this.children.map(c => c.toString()), ].join('\n') + @@ -78,6 +80,7 @@ describe('ReactDebugFiberPerf', () => { ); }, }; + comments = []; // Step one level deeper activeMeasure.children.push(measure); activeMeasure = measure; diff --git a/packages/react-reconciler/src/__tests__/__snapshots__/ReactIncrementalPerf-test.js.snap b/packages/react-reconciler/src/__tests__/__snapshots__/ReactIncrementalPerf-test.js.snap index 57232fd0bb0..acf28b408e9 100644 --- a/packages/react-reconciler/src/__tests__/__snapshots__/ReactIncrementalPerf-test.js.snap +++ b/packages/react-reconciler/src/__tests__/__snapshots__/ReactIncrementalPerf-test.js.snap @@ -2,8 +2,8 @@ exports[`ReactDebugFiberPerf captures all lifecycles 1`] = ` "⚛ (Waiting for async callback...) - // Mount +// Mount ⚛ (React Tree Reconciliation) ⚛ AllLifecycles [mount] ⚛ AllLifecycles.componentWillMount @@ -15,8 +15,8 @@ exports[`ReactDebugFiberPerf captures all lifecycles 1`] = ` ⚛ AllLifecycles.componentDidMount ⚛ (Waiting for async callback...) - // Update +// Update ⚛ (React Tree Reconciliation) ⚛ AllLifecycles [update] ⚛ AllLifecycles.componentWillReceiveProps @@ -30,8 +30,8 @@ exports[`ReactDebugFiberPerf captures all lifecycles 1`] = ` ⚛ AllLifecycles.componentDidUpdate ⚛ (Waiting for async callback...) - // Unmount +// Unmount ⚛ (React Tree Reconciliation) ⚛ (Committing Changes) @@ -43,8 +43,8 @@ exports[`ReactDebugFiberPerf captures all lifecycles 1`] = ` exports[`ReactDebugFiberPerf deduplicates lifecycle names during commit to reduce overhead 1`] = ` "⚛ (Waiting for async callback...) - // The commit phase should mention A and B just once +// The commit phase should mention A and B just once ⚛ (React Tree Reconciliation) ⚛ Parent [update] ⚛ A [update] @@ -59,9 +59,9 @@ exports[`ReactDebugFiberPerf deduplicates lifecycle names during commit to reduc ⚛ B.componentDidUpdate ⚛ (Waiting for async callback...) - // Because of deduplication, we don't know B was cascading, - // but we should still see the warning for the commit phase. +// Because of deduplication, we don't know B was cascading, +// but we should still see the warning for the commit phase. ⚛ (React Tree Reconciliation) ⚛ Parent [update] ⚛ A [update] @@ -105,8 +105,8 @@ exports[`ReactDebugFiberPerf does not schedule an extra callback if setState is exports[`ReactDebugFiberPerf does not treat setState from cWM or cWRP as cascading 1`] = ` "⚛ (Waiting for async callback...) - // Should not print a warning +// Should not print a warning ⚛ (React Tree Reconciliation) ⚛ Parent [mount] ⚛ NotCascading [mount] @@ -117,8 +117,8 @@ exports[`ReactDebugFiberPerf does not treat setState from cWM or cWRP as cascadi ⚛ (Calling Lifecycle Methods: 0 Total) ⚛ (Waiting for async callback...) - // Should not print a warning +// Should not print a warning ⚛ (React Tree Reconciliation) ⚛ Parent [update] ⚛ NotCascading [update] @@ -132,8 +132,8 @@ exports[`ReactDebugFiberPerf does not treat setState from cWM or cWRP as cascadi exports[`ReactDebugFiberPerf measures a simple reconciliation 1`] = ` "⚛ (Waiting for async callback...) - // Mount +// Mount ⚛ (React Tree Reconciliation) ⚛ Parent [mount] ⚛ Child [mount] @@ -143,8 +143,8 @@ exports[`ReactDebugFiberPerf measures a simple reconciliation 1`] = ` ⚛ (Calling Lifecycle Methods: 0 Total) ⚛ (Waiting for async callback...) - // Update +// Update ⚛ (React Tree Reconciliation) ⚛ Parent [update] ⚛ Child [update] @@ -154,8 +154,8 @@ exports[`ReactDebugFiberPerf measures a simple reconciliation 1`] = ` ⚛ (Calling Lifecycle Methods: 2 Total) ⚛ (Waiting for async callback...) - // Unmount +// Unmount ⚛ (React Tree Reconciliation) ⚛ (Committing Changes) @@ -166,23 +166,23 @@ exports[`ReactDebugFiberPerf measures a simple reconciliation 1`] = ` exports[`ReactDebugFiberPerf measures deferred work in chunks 1`] = ` "⚛ (Waiting for async callback...) - // Start mounting Parent and A +// Start mounting Parent and A ⚛ (React Tree Reconciliation) ⚛ Parent [mount] ⚛ A [mount] ⚛ Child [mount] ⚛ (Waiting for async callback...) - // Mount B just a little (but not enough to memoize) +// Mount B just a little (but not enough to memoize) ⚛ (React Tree Reconciliation) ⚛ Parent [mount] ⚛ B [mount] ⚛ (Waiting for async callback...) - // Complete B and Parent +// Complete B and Parent ⚛ (React Tree Reconciliation) ⚛ Parent [mount] ⚛ B [mount] @@ -204,8 +204,8 @@ exports[`ReactDebugFiberPerf measures deprioritized work 1`] = ` ⚛ (Calling Lifecycle Methods: 0 Total) ⚛ (Waiting for async callback...) - // Flush the child +// Flush the child ⚛ (React Tree Reconciliation) ⚛ Child [mount] @@ -217,8 +217,8 @@ exports[`ReactDebugFiberPerf measures deprioritized work 1`] = ` exports[`ReactDebugFiberPerf recovers from caught errors 1`] = ` "⚛ (Waiting for async callback...) - // Stop on Baddie and restart from Boundary +// Stop on Baddie and restart from Boundary ⚛ (React Tree Reconciliation) ⚛ Parent [mount] ⛔ Boundary [mount] Warning: An error was thrown inside this error boundary @@ -242,8 +242,8 @@ exports[`ReactDebugFiberPerf recovers from caught errors 1`] = ` exports[`ReactDebugFiberPerf recovers from fatal errors 1`] = ` "⚛ (Waiting for async callback...) - // Will fatal +// Will fatal ⚛ (React Tree Reconciliation) ⚛ Parent [mount] ⚛ Baddie [mount] @@ -253,8 +253,8 @@ exports[`ReactDebugFiberPerf recovers from fatal errors 1`] = ` ⚛ (Calling Lifecycle Methods: 1 Total) ⚛ (Waiting for async callback...) - // Will reconcile from a clean state +// Will reconcile from a clean state ⚛ (React Tree Reconciliation) ⚛ Parent [mount] ⚛ Child [mount] @@ -267,8 +267,8 @@ exports[`ReactDebugFiberPerf recovers from fatal errors 1`] = ` exports[`ReactDebugFiberPerf skips parents during setState 1`] = ` "⚛ (Waiting for async callback...) - // Should include just A and B, no Parents +// Should include just A and B, no Parents ⚛ (React Tree Reconciliation) ⚛ A [update] ⚛ B [update] @@ -346,8 +346,8 @@ exports[`ReactDebugFiberPerf warns if async work expires (starvation) 1`] = ` exports[`ReactDebugFiberPerf warns on cascading renders from setState 1`] = ` "⚛ (Waiting for async callback...) - // Should print a warning +// Should print a warning ⚛ (React Tree Reconciliation) ⚛ Parent [mount] ⚛ Cascading [mount] @@ -368,8 +368,8 @@ exports[`ReactDebugFiberPerf warns on cascading renders from setState 1`] = ` exports[`ReactDebugFiberPerf warns on cascading renders from top-level render 1`] = ` "⚛ (Waiting for async callback...) - // Rendering the first root +// Rendering the first root ⚛ (React Tree Reconciliation) ⚛ Cascading [mount] @@ -377,8 +377,8 @@ exports[`ReactDebugFiberPerf warns on cascading renders from top-level render 1` ⚛ (Committing Host Effects: 1 Total) ⚛ (Calling Lifecycle Methods: 1 Total) ⛔ Cascading.componentDidMount Warning: Scheduled a cascading update - // Scheduling another root from componentDidMount +// Scheduling another root from componentDidMount ⚛ (React Tree Reconciliation) ⚛ Child [mount] From f475a52811d93d1bf02285c0e555c95ed0b02f6d Mon Sep 17 00:00:00 2001 From: Andrew Clark Date: Fri, 10 Nov 2017 12:11:50 +0000 Subject: [PATCH 3/3] Better warning message Most users won't know what "expires" means --- packages/react-reconciler/src/ReactDebugFiberPerf.js | 2 +- .../__tests__/__snapshots__/ReactIncrementalPerf-test.js.snap | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/packages/react-reconciler/src/ReactDebugFiberPerf.js b/packages/react-reconciler/src/ReactDebugFiberPerf.js index 1f239f7d6a0..e068baf946a 100644 --- a/packages/react-reconciler/src/ReactDebugFiberPerf.js +++ b/packages/react-reconciler/src/ReactDebugFiberPerf.js @@ -245,7 +245,7 @@ export function stopRequestCallbackTimer(didExpire: boolean): void { if (enableUserTimingAPI) { if (supportsUserTiming) { isWaitingForCallback = false; - const warning = didExpire ? 'Async work expired' : null; + const warning = didExpire ? 'React was blocked by main thread' : null; endMark( '(Waiting for async callback...)', '(Waiting for async callback...)', diff --git a/packages/react-reconciler/src/__tests__/__snapshots__/ReactIncrementalPerf-test.js.snap b/packages/react-reconciler/src/__tests__/__snapshots__/ReactIncrementalPerf-test.js.snap index acf28b408e9..1d588f3e7fe 100644 --- a/packages/react-reconciler/src/__tests__/__snapshots__/ReactIncrementalPerf-test.js.snap +++ b/packages/react-reconciler/src/__tests__/__snapshots__/ReactIncrementalPerf-test.js.snap @@ -333,7 +333,7 @@ exports[`ReactDebugFiberPerf warns if an in-progress update is interrupted 1`] = `; exports[`ReactDebugFiberPerf warns if async work expires (starvation) 1`] = ` -"⛔ (Waiting for async callback...) Warning: Async work expired +"⛔ (Waiting for async callback...) Warning: React was blocked by main thread ⚛ (React Tree Reconciliation) ⚛ Foo [mount]