@samitouri / QOS-React-2 / commits / a1b1e391e1

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.

Andrew Clark committed Jul 12, 2022 at 12:57 UTC a1b1e391e1663a401b84642b2107390fe9451a38
2 files changed +248 -132
packages/react-reconciler/src/ReactFiberCommitWork.new.js
+124 -66
@@ -733,11 +733,19 @@ function commitLayoutEffectOnFiber(
733 HookLayout | HookHasEffect,
734 finishedWork,
735 );
736 - } finally {
737 - recordLayoutEffectDuration(finishedWork);
736 + } catch (error) {
737 + captureCommitPhaseError(finishedWork, finishedWork.return, error);
738 }
739 + recordLayoutEffectDuration(finishedWork);
740 } 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 + }
749 }
750 }
751 }
@@ -786,11 +794,24 @@ function commitLayoutEffectOnFiber(
794 try {
795 startLayoutEffectTimer();
796 instance.componentDidMount();
789 - } finally {
790 - recordLayoutEffectDuration(finishedWork);
797 + } catch (error) {
798 + captureCommitPhaseError(
799 + finishedWork,
800 + finishedWork.return,
801 + error,
802 + );
803 }
804 + recordLayoutEffectDuration(finishedWork);
805 } else {
793 - instance.componentDidMount();
806 + try {
807 + instance.componentDidMount();
808 + } catch (error) {
809 + captureCommitPhaseError(
810 + finishedWork,
811 + finishedWork.return,
812 + error,
813 + );
814 + }
815 }
816 } else {
817 const prevProps =
@@ -840,15 +861,28 @@ function commitLayoutEffectOnFiber(
861 prevState,
862 instance.__reactInternalSnapshotBeforeUpdate,
863 );
843 - } finally {
844 - recordLayoutEffectDuration(finishedWork);
864 + } catch (error) {
865 + captureCommitPhaseError(
866 + finishedWork,
867 + finishedWork.return,
868 + error,
869 + );
870 }
871 + recordLayoutEffectDuration(finishedWork);
872 } 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 + }
886 }
887 }
888 }
@@ -892,13 +926,21 @@ function commitLayoutEffectOnFiber(
926 // We could update instance props and state here,
927 // but instead we rely on them being set during last render.
928 // 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 + }
934 }
935 }
936
937 if (flags & Ref) {
938 if (!offscreenSubtreeWasHidden) {
901 - commitAttachRef(finishedWork);
939 + try {
940 + commitAttachRef(finishedWork);
941 + } catch (error) {
942 + captureCommitPhaseError(finishedWork, finishedWork.return, error);
943 + }
944 }
945 }
946 break;
@@ -922,7 +964,11 @@ function commitLayoutEffectOnFiber(
964 break;
965 }
966 }
925 - commitCallbacks(updateQueue, instance);
967 + try {
968 + commitCallbacks(updateQueue, instance);
969 + } catch (error) {
970 + captureCommitPhaseError(finishedWork, finishedWork.return, error);
971 + }
972 }
973 }
974 break;
@@ -938,13 +984,21 @@ function commitLayoutEffectOnFiber(
984 if (current === null && finishedWork.flags & Update) {
985 const type = finishedWork.type;
986 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 + }
992 }
993 }
994
995 if (flags & Ref) {
996 if (!offscreenSubtreeWasHidden) {
947 - commitAttachRef(finishedWork);
997 + try {
998 + commitAttachRef(finishedWork);
999 + } catch (error) {
1000 + captureCommitPhaseError(finishedWork, finishedWork.return, error);
1001 + }
1002 }
1003 }
1004 break;
@@ -960,60 +1014,64 @@ function commitLayoutEffectOnFiber(
1014 case Profiler: {
1015 if (enableProfilerTimer) {
1016 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;
1020
966 - const commitTime = getCommitTime();
1021 + const commitTime = getCommitTime();
1022
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 + }
1028 }
973 - }
1029
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(
1032 finishedWork.memoizedProps.id,
1033 phase,
991 - effectDuration,
1034 + finishedWork.actualDuration,
1035 + finishedWork.treeBaseDuration,
1036 + finishedWork.actualStartTime,
1037 commitTime,
1038 );
1039 }
1040
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;
1071 }
1015 - parentFiber = parentFiber.return;
1072 }
1073 + } catch (error) {
1074 + captureCommitPhaseError(finishedWork, finishedWork.return, error);
1075 }
1076 }
1077 }
@@ -1021,7 +1079,11 @@ function commitLayoutEffectOnFiber(
1079 }
1080 case SuspenseComponent: {
1081 if (flags & Update) {
1024 - commitSuspenseHydrationCallbacks(finishedRoot, finishedWork);
1082 + try {
1083 + commitSuspenseHydrationCallbacks(finishedRoot, finishedWork);
1084 + } catch (error) {
1085 + captureCommitPhaseError(finishedWork, finishedWork.return, error);
1086 + }
1087 }
1088 break;
1089 }
@@ -2617,11 +2679,7 @@ function commitLayoutMountEffects_complete(
2679 if ((fiber.flags & LayoutMask) !== NoFlags) {
2680 const current = fiber.alternate;
2681 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);
2683 resetCurrentDebugFiberInDEV();
2684 }
2685
packages/react-reconciler/src/ReactFiberCommitWork.old.js
+124 -66
@@ -733,11 +733,19 @@ function commitLayoutEffectOnFiber(
733 HookLayout | HookHasEffect,
734 finishedWork,
735 );
736 - } finally {
737 - recordLayoutEffectDuration(finishedWork);
736 + } catch (error) {
737 + captureCommitPhaseError(finishedWork, finishedWork.return, error);
738 }
739 + recordLayoutEffectDuration(finishedWork);
740 } 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 + }
749 }
750 }
751 }
@@ -786,11 +794,24 @@ function commitLayoutEffectOnFiber(
794 try {
795 startLayoutEffectTimer();
796 instance.componentDidMount();
789 - } finally {
790 - recordLayoutEffectDuration(finishedWork);
797 + } catch (error) {
798 + captureCommitPhaseError(
799 + finishedWork,
800 + finishedWork.return,
801 + error,
802 + );
803 }
804 + recordLayoutEffectDuration(finishedWork);
805 } else {
793 - instance.componentDidMount();
806 + try {
807 + instance.componentDidMount();
808 + } catch (error) {
809 + captureCommitPhaseError(
810 + finishedWork,
811 + finishedWork.return,
812 + error,
813 + );
814 + }
815 }
816 } else {
817 const prevProps =
@@ -840,15 +861,28 @@ function commitLayoutEffectOnFiber(
861 prevState,
862 instance.__reactInternalSnapshotBeforeUpdate,
863 );
843 - } finally {
844 - recordLayoutEffectDuration(finishedWork);
864 + } catch (error) {
865 + captureCommitPhaseError(
866 + finishedWork,
867 + finishedWork.return,
868 + error,
869 + );
870 }
871 + recordLayoutEffectDuration(finishedWork);
872 } 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 + }
886 }
887 }
888 }
@@ -892,13 +926,21 @@ function commitLayoutEffectOnFiber(
926 // We could update instance props and state here,
927 // but instead we rely on them being set during last render.
928 // 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 + }
934 }
935 }
936
937 if (flags & Ref) {
938 if (!offscreenSubtreeWasHidden) {
901 - commitAttachRef(finishedWork);
939 + try {
940 + commitAttachRef(finishedWork);
941 + } catch (error) {
942 + captureCommitPhaseError(finishedWork, finishedWork.return, error);
943 + }
944 }
945 }
946 break;
@@ -922,7 +964,11 @@ function commitLayoutEffectOnFiber(
964 break;
965 }
966 }
925 - commitCallbacks(updateQueue, instance);
967 + try {
968 + commitCallbacks(updateQueue, instance);
969 + } catch (error) {
970 + captureCommitPhaseError(finishedWork, finishedWork.return, error);
971 + }
972 }
973 }
974 break;
@@ -938,13 +984,21 @@ function commitLayoutEffectOnFiber(
984 if (current === null && finishedWork.flags & Update) {
985 const type = finishedWork.type;
986 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 + }
992 }
993 }
994
995 if (flags & Ref) {
996 if (!offscreenSubtreeWasHidden) {
947 - commitAttachRef(finishedWork);
997 + try {
998 + commitAttachRef(finishedWork);
999 + } catch (error) {
1000 + captureCommitPhaseError(finishedWork, finishedWork.return, error);
1001 + }
1002 }
1003 }
1004 break;
@@ -960,60 +1014,64 @@ function commitLayoutEffectOnFiber(
1014 case Profiler: {
1015 if (enableProfilerTimer) {
1016 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;
1020
966 - const commitTime = getCommitTime();
1021 + const commitTime = getCommitTime();
1022
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 + }
1028 }
973 - }
1029
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(
1032 finishedWork.memoizedProps.id,
1033 phase,
991 - effectDuration,
1034 + finishedWork.actualDuration,
1035 + finishedWork.treeBaseDuration,
1036 + finishedWork.actualStartTime,
1037 commitTime,
1038 );
1039 }
1040
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;
1071 }
1015 - parentFiber = parentFiber.return;
1072 }
1073 + } catch (error) {
1074 + captureCommitPhaseError(finishedWork, finishedWork.return, error);
1075 }
1076 }
1077 }
@@ -1021,7 +1079,11 @@ function commitLayoutEffectOnFiber(
1079 }
1080 case SuspenseComponent: {
1081 if (flags & Update) {
1024 - commitSuspenseHydrationCallbacks(finishedRoot, finishedWork);
1082 + try {
1083 + commitSuspenseHydrationCallbacks(finishedRoot, finishedWork);
1084 + } catch (error) {
1085 + captureCommitPhaseError(finishedWork, finishedWork.return, error);
1086 + }
1087 }
1088 break;
1089 }
@@ -2617,11 +2679,7 @@ function commitLayoutMountEffects_complete(
2679 if ((fiber.flags & LayoutMask) !== NoFlags) {
2680 const current = fiber.alternate;
2681 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);
2683 resetCurrentDebugFiberInDEV();
2684 }
2685