Skip to content

Commit bf6a968

Browse files
author
Brian Vaughn
committed
Scheduling Profiler marks should include thrown Errors
1 parent 1c58cfa commit bf6a968

6 files changed

Lines changed: 202 additions & 20 deletions

File tree

packages/react-reconciler/src/ReactFiberThrow.new.js

Lines changed: 0 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -41,7 +41,6 @@ import {shouldCaptureSuspense} from './ReactFiberSuspenseComponent.new';
4141
import {NoMode, ConcurrentMode, DebugTracingMode} from './ReactTypeOfMode';
4242
import {
4343
enableDebugTracing,
44-
enableSchedulingProfiler,
4544
enableLazyContextPropagation,
4645
enableUpdaterTracking,
4746
enablePersistentOffscreenHostContainer,
@@ -71,10 +70,6 @@ import {
7170
import {propagateParentContextChangesToDeferredTree} from './ReactFiberNewContext.new';
7271
import {logCapturedError} from './ReactFiberErrorLogger';
7372
import {logComponentSuspended} from './DebugTracing';
74-
import {
75-
markComponentRenderStopped,
76-
markComponentSuspended,
77-
} from './SchedulingProfiler';
7873
import {isDevToolsPresent} from './ReactFiberDevToolsHook.new';
7974
import {
8075
SyncLane,
@@ -247,11 +242,6 @@ function throwException(
247242
}
248243
}
249244

250-
if (enableSchedulingProfiler) {
251-
markComponentRenderStopped();
252-
markComponentSuspended(sourceFiber, wakeable, rootRenderLanes);
253-
}
254-
255245
// Reset the memoizedState to what it was before we attempted to render it.
256246
// A legacy mode Suspense quirk, only relevant to hook components.
257247
const tag = sourceFiber.tag;

packages/react-reconciler/src/ReactFiberThrow.old.js

Lines changed: 0 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -41,7 +41,6 @@ import {shouldCaptureSuspense} from './ReactFiberSuspenseComponent.old';
4141
import {NoMode, ConcurrentMode, DebugTracingMode} from './ReactTypeOfMode';
4242
import {
4343
enableDebugTracing,
44-
enableSchedulingProfiler,
4544
enableLazyContextPropagation,
4645
enableUpdaterTracking,
4746
enablePersistentOffscreenHostContainer,
@@ -71,10 +70,6 @@ import {
7170
import {propagateParentContextChangesToDeferredTree} from './ReactFiberNewContext.old';
7271
import {logCapturedError} from './ReactFiberErrorLogger';
7372
import {logComponentSuspended} from './DebugTracing';
74-
import {
75-
markComponentRenderStopped,
76-
markComponentSuspended,
77-
} from './SchedulingProfiler';
7873
import {isDevToolsPresent} from './ReactFiberDevToolsHook.old';
7974
import {
8075
SyncLane,
@@ -247,11 +242,6 @@ function throwException(
247242
}
248243
}
249244

250-
if (enableSchedulingProfiler) {
251-
markComponentRenderStopped();
252-
markComponentSuspended(sourceFiber, wakeable, rootRenderLanes);
253-
}
254-
255245
// Reset the memoizedState to what it was before we attempted to render it.
256246
// A legacy mode Suspense quirk, only relevant to hook components.
257247
const tag = sourceFiber.tag;

packages/react-reconciler/src/ReactFiberWorkLoop.new.js

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -68,6 +68,9 @@ import {
6868
import {
6969
markCommitStarted,
7070
markCommitStopped,
71+
markComponentRenderStopped,
72+
markComponentSuspended,
73+
markComponentErrored,
7174
markLayoutEffectsStarted,
7275
markLayoutEffectsStopped,
7376
markPassiveEffectsStarted,
@@ -1356,6 +1359,21 @@ function handleError(root, thrownValue): void {
13561359
stopProfilerTimerIfRunningAndRecordDelta(erroredWork, true);
13571360
}
13581361

1362+
if (enableSchedulingProfiler) {
1363+
markComponentRenderStopped();
1364+
1365+
if (
1366+
thrownValue !== null &&
1367+
typeof thrownValue === 'object' &&
1368+
typeof thrownValue.then === 'function'
1369+
) {
1370+
const wakeable: Wakeable = (thrownValue: any);
1371+
markComponentSuspended(erroredWork, wakeable, workInProgressRootRenderLanes);
1372+
} else {
1373+
markComponentErrored(erroredWork, thrownValue, workInProgressRootRenderLanes);
1374+
}
1375+
}
1376+
13591377
throwException(
13601378
root,
13611379
erroredWork.return,

packages/react-reconciler/src/ReactFiberWorkLoop.old.js

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -68,6 +68,9 @@ import {
6868
import {
6969
markCommitStarted,
7070
markCommitStopped,
71+
markComponentRenderStopped,
72+
markComponentSuspended,
73+
markComponentErrored,
7174
markLayoutEffectsStarted,
7275
markLayoutEffectsStopped,
7376
markPassiveEffectsStarted,
@@ -1356,6 +1359,21 @@ function handleError(root, thrownValue): void {
13561359
stopProfilerTimerIfRunningAndRecordDelta(erroredWork, true);
13571360
}
13581361

1362+
if (enableSchedulingProfiler) {
1363+
markComponentRenderStopped();
1364+
1365+
if (
1366+
thrownValue !== null &&
1367+
typeof thrownValue === 'object' &&
1368+
typeof thrownValue.then === 'function'
1369+
) {
1370+
const wakeable: Wakeable = (thrownValue: any);
1371+
markComponentSuspended(erroredWork, wakeable, workInProgressRootRenderLanes);
1372+
} else {
1373+
markComponentErrored(erroredWork, thrownValue, workInProgressRootRenderLanes);
1374+
}
1375+
}
1376+
13591377
throwException(
13601378
root,
13611379
erroredWork.return,

packages/react-reconciler/src/SchedulingProfiler.js

Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -144,6 +144,33 @@ export function markComponentRenderStopped(): void {
144144
}
145145
}
146146

147+
export function markComponentErrored(
148+
fiber: Fiber,
149+
thrownValue: mixed,
150+
lanes: Lanes,
151+
): void {
152+
if (enableSchedulingProfiler) {
153+
if (supportsUserTimingV3) {
154+
const componentName = getComponentNameFromFiber(fiber) || 'Unknown';
155+
const phase = fiber.alternate === null ? 'mount' : 'update';
156+
157+
let message = '';
158+
if (
159+
thrownValue !== null &&
160+
typeof thrownValue === 'object' &&
161+
typeof thrownValue.message === 'string'
162+
) {
163+
message = thrownValue.message;
164+
} else if (typeof thrownValue === 'string') {
165+
message = thrownValue;
166+
}
167+
168+
// TODO (scheduling profiler) Add component stack id
169+
markAndClear(`--error-${componentName}-${phase}-${message}`);
170+
}
171+
}
172+
}
173+
147174
const PossiblyWeakMap = typeof WeakMap === 'function' ? WeakMap : Map;
148175

149176
// $FlowFixMe: Flow cannot handle polymorphic WeakMaps

packages/react-reconciler/src/__tests__/SchedulingProfiler-test.internal.js

Lines changed: 139 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -715,4 +715,143 @@ describe('SchedulingProfiler', () => {
715715
`);
716716
}
717717
});
718+
719+
it('should mark sync render that throws', async () => {
720+
class ErrorBoundary extends React.Component {
721+
state = {error: null};
722+
componentDidCatch(error) {
723+
this.setState({error});
724+
}
725+
render() {
726+
if (this.state.error) {
727+
return null;
728+
}
729+
return this.props.children;
730+
}
731+
}
732+
733+
function ExampleThatThrows() {
734+
throw Error('Expected error');
735+
}
736+
737+
ReactTestRenderer.create(
738+
<ErrorBoundary>
739+
<ExampleThatThrows />
740+
</ErrorBoundary>,
741+
);
742+
743+
if (gate(flags => flags.enableSchedulingProfiler)) {
744+
expect(getMarks()).toMatchInlineSnapshot(`
745+
Array [
746+
"--schedule-render-1",
747+
"--render-start-1",
748+
"--component-render-start-ErrorBoundary",
749+
"--component-render-stop",
750+
"--component-render-start-ExampleThatThrows",
751+
"--component-render-start-ExampleThatThrows",
752+
"--component-render-stop",
753+
"--error-ExampleThatThrows-mount-Expected error",
754+
"--render-stop",
755+
"--commit-start-1",
756+
"--react-version-17.0.3",
757+
"--profiler-version-1",
758+
"--react-lane-labels-Sync,InputContinuousHydration,InputContinuous,DefaultHydration,Default,TransitionHydration,Transition,Transition,Transition,Transition,Transition,Transition,Transition,Transition,Transition,Transition,Transition,Transition,Transition,Transition,Transition,Transition,Retry,Retry,Retry,Retry,Retry,SelectiveHydration,IdleHydration,Idle,Offscreen",
759+
"--layout-effects-start-1",
760+
"--schedule-state-update-1-ErrorBoundary",
761+
"--layout-effects-stop",
762+
"--commit-stop",
763+
"--render-start-1",
764+
"--component-render-start-ErrorBoundary",
765+
"--component-render-stop",
766+
"--render-stop",
767+
"--commit-start-1",
768+
"--react-version-17.0.3",
769+
"--profiler-version-1",
770+
"--react-lane-labels-Sync,InputContinuousHydration,InputContinuous,DefaultHydration,Default,TransitionHydration,Transition,Transition,Transition,Transition,Transition,Transition,Transition,Transition,Transition,Transition,Transition,Transition,Transition,Transition,Transition,Transition,Retry,Retry,Retry,Retry,Retry,SelectiveHydration,IdleHydration,Idle,Offscreen",
771+
"--commit-stop",
772+
]
773+
`);
774+
}
775+
});
776+
777+
it('should mark concurrent render that throws', async () => {
778+
class ErrorBoundary extends React.Component {
779+
state = {error: null};
780+
componentDidCatch(error) {
781+
this.setState({error});
782+
}
783+
render() {
784+
if (this.state.error) {
785+
return null;
786+
}
787+
return this.props.children;
788+
}
789+
}
790+
791+
function ExampleThatThrows() {
792+
// eslint-disable-next-line no-throw-literal
793+
throw 'Expected error';
794+
}
795+
796+
ReactTestRenderer.create(
797+
<ErrorBoundary>
798+
<ExampleThatThrows />
799+
</ErrorBoundary>,
800+
{unstable_isConcurrent: true},
801+
);
802+
803+
if (gate(flags => flags.enableSchedulingProfiler)) {
804+
expect(getMarks()).toMatchInlineSnapshot(`
805+
Array [
806+
"--schedule-render-16",
807+
]
808+
`);
809+
}
810+
811+
clearPendingMarks();
812+
813+
expect(Scheduler).toFlushUntilNextPaint([]);
814+
815+
if (gate(flags => flags.enableSchedulingProfiler)) {
816+
if (gate(flags => flags.enableSchedulingProfiler)) {
817+
expect(getMarks()).toMatchInlineSnapshot(`
818+
Array [
819+
"--render-start-16",
820+
"--component-render-start-ErrorBoundary",
821+
"--component-render-stop",
822+
"--component-render-start-ExampleThatThrows",
823+
"--component-render-start-ExampleThatThrows",
824+
"--component-render-stop",
825+
"--error-ExampleThatThrows-mount-Expected error",
826+
"--render-stop",
827+
"--render-start-16",
828+
"--component-render-start-ErrorBoundary",
829+
"--component-render-stop",
830+
"--component-render-start-ExampleThatThrows",
831+
"--component-render-start-ExampleThatThrows",
832+
"--component-render-stop",
833+
"--error-ExampleThatThrows-mount-Expected error",
834+
"--render-stop",
835+
"--commit-start-16",
836+
"--react-version-17.0.3",
837+
"--profiler-version-1",
838+
"--react-lane-labels-Sync,InputContinuousHydration,InputContinuous,DefaultHydration,Default,TransitionHydration,Transition,Transition,Transition,Transition,Transition,Transition,Transition,Transition,Transition,Transition,Transition,Transition,Transition,Transition,Transition,Transition,Retry,Retry,Retry,Retry,Retry,SelectiveHydration,IdleHydration,Idle,Offscreen",
839+
"--layout-effects-start-16",
840+
"--schedule-state-update-1-ErrorBoundary",
841+
"--layout-effects-stop",
842+
"--render-start-1",
843+
"--component-render-start-ErrorBoundary",
844+
"--component-render-stop",
845+
"--render-stop",
846+
"--commit-start-1",
847+
"--react-version-17.0.3",
848+
"--profiler-version-1",
849+
"--react-lane-labels-Sync,InputContinuousHydration,InputContinuous,DefaultHydration,Default,TransitionHydration,Transition,Transition,Transition,Transition,Transition,Transition,Transition,Transition,Transition,Transition,Transition,Transition,Transition,Transition,Transition,Transition,Retry,Retry,Retry,Retry,Retry,SelectiveHydration,IdleHydration,Idle,Offscreen",
850+
"--commit-stop",
851+
"--commit-stop",
852+
]
853+
`);
854+
}
855+
}
856+
});
718857
});

0 commit comments

Comments
 (0)