Skip to content

Commit a1b1e39

Browse files
committed
Wrap try-catch directly around each user function
(This is the same as f9e6aef, but for the layout phase rather than the mutation phase.) This moves the try-catch from around each fiber's layout phase to direclty around each user function (effect function, callback, etc). We already do this when unmounting because if one unmount function errors, we still need to call all the others so they can clean up their resources. Previously we didn't bother to do this for anything but unmount, because if a mount effect throws, we're going to delete that whole tree anyway. But now that we're switching from an iterative loop to a recursive one, we don't want every call frame on the stack to have a try-catch, since the error handling requires additional memory. Wrapping every user function is a bit tedious, but it's better for performance. Many of them already had try blocks around them already.
1 parent 3df7e8f commit a1b1e39

File tree

2 files changed

+248
-132
lines changed

2 files changed

+248
-132
lines changed

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

Lines changed: 124 additions & 66 deletions
Original file line numberDiff line numberDiff line change
@@ -733,11 +733,19 @@ function commitLayoutEffectOnFiber(
733733
HookLayout | HookHasEffect,
734734
finishedWork,
735735
);
736-
} finally {
737-
recordLayoutEffectDuration(finishedWork);
736+
} catch (error) {
737+
captureCommitPhaseError(finishedWork, finishedWork.return, error);
738738
}
739+
recordLayoutEffectDuration(finishedWork);
739740
} else {
740-
commitHookEffectListMount(HookLayout | HookHasEffect, finishedWork);
741+
try {
742+
commitHookEffectListMount(
743+
HookLayout | HookHasEffect,
744+
finishedWork,
745+
);
746+
} catch (error) {
747+
captureCommitPhaseError(finishedWork, finishedWork.return, error);
748+
}
741749
}
742750
}
743751
}
@@ -786,11 +794,24 @@ function commitLayoutEffectOnFiber(
786794
try {
787795
startLayoutEffectTimer();
788796
instance.componentDidMount();
789-
} finally {
790-
recordLayoutEffectDuration(finishedWork);
797+
} catch (error) {
798+
captureCommitPhaseError(
799+
finishedWork,
800+
finishedWork.return,
801+
error,
802+
);
791803
}
804+
recordLayoutEffectDuration(finishedWork);
792805
} else {
793-
instance.componentDidMount();
806+
try {
807+
instance.componentDidMount();
808+
} catch (error) {
809+
captureCommitPhaseError(
810+
finishedWork,
811+
finishedWork.return,
812+
error,
813+
);
814+
}
794815
}
795816
} else {
796817
const prevProps =
@@ -840,15 +861,28 @@ function commitLayoutEffectOnFiber(
840861
prevState,
841862
instance.__reactInternalSnapshotBeforeUpdate,
842863
);
843-
} finally {
844-
recordLayoutEffectDuration(finishedWork);
864+
} catch (error) {
865+
captureCommitPhaseError(
866+
finishedWork,
867+
finishedWork.return,
868+
error,
869+
);
845870
}
871+
recordLayoutEffectDuration(finishedWork);
846872
} else {
847-
instance.componentDidUpdate(
848-
prevProps,
849-
prevState,
850-
instance.__reactInternalSnapshotBeforeUpdate,
851-
);
873+
try {
874+
instance.componentDidUpdate(
875+
prevProps,
876+
prevState,
877+
instance.__reactInternalSnapshotBeforeUpdate,
878+
);
879+
} catch (error) {
880+
captureCommitPhaseError(
881+
finishedWork,
882+
finishedWork.return,
883+
error,
884+
);
885+
}
852886
}
853887
}
854888
}
@@ -892,13 +926,21 @@ function commitLayoutEffectOnFiber(
892926
// We could update instance props and state here,
893927
// but instead we rely on them being set during last render.
894928
// TODO: revisit this when we implement resuming.
895-
commitCallbacks(updateQueue, instance);
929+
try {
930+
commitCallbacks(updateQueue, instance);
931+
} catch (error) {
932+
captureCommitPhaseError(finishedWork, finishedWork.return, error);
933+
}
896934
}
897935
}
898936

899937
if (flags & Ref) {
900938
if (!offscreenSubtreeWasHidden) {
901-
commitAttachRef(finishedWork);
939+
try {
940+
commitAttachRef(finishedWork);
941+
} catch (error) {
942+
captureCommitPhaseError(finishedWork, finishedWork.return, error);
943+
}
902944
}
903945
}
904946
break;
@@ -922,7 +964,11 @@ function commitLayoutEffectOnFiber(
922964
break;
923965
}
924966
}
925-
commitCallbacks(updateQueue, instance);
967+
try {
968+
commitCallbacks(updateQueue, instance);
969+
} catch (error) {
970+
captureCommitPhaseError(finishedWork, finishedWork.return, error);
971+
}
926972
}
927973
}
928974
break;
@@ -938,13 +984,21 @@ function commitLayoutEffectOnFiber(
938984
if (current === null && finishedWork.flags & Update) {
939985
const type = finishedWork.type;
940986
const props = finishedWork.memoizedProps;
941-
commitMount(instance, type, props, finishedWork);
987+
try {
988+
commitMount(instance, type, props, finishedWork);
989+
} catch (error) {
990+
captureCommitPhaseError(finishedWork, finishedWork.return, error);
991+
}
942992
}
943993
}
944994

945995
if (flags & Ref) {
946996
if (!offscreenSubtreeWasHidden) {
947-
commitAttachRef(finishedWork);
997+
try {
998+
commitAttachRef(finishedWork);
999+
} catch (error) {
1000+
captureCommitPhaseError(finishedWork, finishedWork.return, error);
1001+
}
9481002
}
9491003
}
9501004
break;
@@ -960,68 +1014,76 @@ function commitLayoutEffectOnFiber(
9601014
case Profiler: {
9611015
if (enableProfilerTimer) {
9621016
if (flags & Update) {
963-
const {onCommit, onRender} = finishedWork.memoizedProps;
964-
const {effectDuration} = finishedWork.stateNode;
1017+
try {
1018+
const {onCommit, onRender} = finishedWork.memoizedProps;
1019+
const {effectDuration} = finishedWork.stateNode;
9651020

966-
const commitTime = getCommitTime();
1021+
const commitTime = getCommitTime();
9671022

968-
let phase = current === null ? 'mount' : 'update';
969-
if (enableProfilerNestedUpdatePhase) {
970-
if (isCurrentUpdateNested()) {
971-
phase = 'nested-update';
1023+
let phase = current === null ? 'mount' : 'update';
1024+
if (enableProfilerNestedUpdatePhase) {
1025+
if (isCurrentUpdateNested()) {
1026+
phase = 'nested-update';
1027+
}
9721028
}
973-
}
9741029

975-
if (typeof onRender === 'function') {
976-
onRender(
977-
finishedWork.memoizedProps.id,
978-
phase,
979-
finishedWork.actualDuration,
980-
finishedWork.treeBaseDuration,
981-
finishedWork.actualStartTime,
982-
commitTime,
983-
);
984-
}
985-
986-
if (enableProfilerCommitHooks) {
987-
if (typeof onCommit === 'function') {
988-
onCommit(
1030+
if (typeof onRender === 'function') {
1031+
onRender(
9891032
finishedWork.memoizedProps.id,
9901033
phase,
991-
effectDuration,
1034+
finishedWork.actualDuration,
1035+
finishedWork.treeBaseDuration,
1036+
finishedWork.actualStartTime,
9921037
commitTime,
9931038
);
9941039
}
9951040

996-
// Schedule a passive effect for this Profiler to call onPostCommit hooks.
997-
// This effect should be scheduled even if there is no onPostCommit callback for this Profiler,
998-
// because the effect is also where times bubble to parent Profilers.
999-
enqueuePendingPassiveProfilerEffect(finishedWork);
1000-
1001-
// Propagate layout effect durations to the next nearest Profiler ancestor.
1002-
// Do not reset these values until the next render so DevTools has a chance to read them first.
1003-
let parentFiber = finishedWork.return;
1004-
outer: while (parentFiber !== null) {
1005-
switch (parentFiber.tag) {
1006-
case HostRoot:
1007-
const root = parentFiber.stateNode;
1008-
root.effectDuration += effectDuration;
1009-
break outer;
1010-
case Profiler:
1011-
const parentStateNode = parentFiber.stateNode;
1012-
parentStateNode.effectDuration += effectDuration;
1013-
break outer;
1041+
if (enableProfilerCommitHooks) {
1042+
if (typeof onCommit === 'function') {
1043+
onCommit(
1044+
finishedWork.memoizedProps.id,
1045+
phase,
1046+
effectDuration,
1047+
commitTime,
1048+
);
1049+
}
1050+
1051+
// Schedule a passive effect for this Profiler to call onPostCommit hooks.
1052+
// This effect should be scheduled even if there is no onPostCommit callback for this Profiler,
1053+
// because the effect is also where times bubble to parent Profilers.
1054+
enqueuePendingPassiveProfilerEffect(finishedWork);
1055+
1056+
// Propagate layout effect durations to the next nearest Profiler ancestor.
1057+
// Do not reset these values until the next render so DevTools has a chance to read them first.
1058+
let parentFiber = finishedWork.return;
1059+
outer: while (parentFiber !== null) {
1060+
switch (parentFiber.tag) {
1061+
case HostRoot:
1062+
const root = parentFiber.stateNode;
1063+
root.effectDuration += effectDuration;
1064+
break outer;
1065+
case Profiler:
1066+
const parentStateNode = parentFiber.stateNode;
1067+
parentStateNode.effectDuration += effectDuration;
1068+
break outer;
1069+
}
1070+
parentFiber = parentFiber.return;
10141071
}
1015-
parentFiber = parentFiber.return;
10161072
}
1073+
} catch (error) {
1074+
captureCommitPhaseError(finishedWork, finishedWork.return, error);
10171075
}
10181076
}
10191077
}
10201078
break;
10211079
}
10221080
case SuspenseComponent: {
10231081
if (flags & Update) {
1024-
commitSuspenseHydrationCallbacks(finishedRoot, finishedWork);
1082+
try {
1083+
commitSuspenseHydrationCallbacks(finishedRoot, finishedWork);
1084+
} catch (error) {
1085+
captureCommitPhaseError(finishedWork, finishedWork.return, error);
1086+
}
10251087
}
10261088
break;
10271089
}
@@ -2617,11 +2679,7 @@ function commitLayoutMountEffects_complete(
26172679
if ((fiber.flags & LayoutMask) !== NoFlags) {
26182680
const current = fiber.alternate;
26192681
setCurrentDebugFiberInDEV(fiber);
2620-
try {
2621-
commitLayoutEffectOnFiber(root, current, fiber, committedLanes);
2622-
} catch (error) {
2623-
captureCommitPhaseError(fiber, fiber.return, error);
2624-
}
2682+
commitLayoutEffectOnFiber(root, current, fiber, committedLanes);
26252683
resetCurrentDebugFiberInDEV();
26262684
}
26272685

0 commit comments

Comments
 (0)