Skip to content

Commit e6a0f27

Browse files
author
Brian Vaughn
authored
Profiler: Improve nested-update checks (#20299)
Previous checks were too naive when it comes to pending lower-pri work or batched updates. This commit adds two new (previously failing) tests and fixes.
1 parent 2cf8f7f commit e6a0f27

File tree

3 files changed

+93
-9
lines changed

3 files changed

+93
-9
lines changed

Diff for: packages/react-reconciler/src/ReactFiberWorkLoop.new.js

+2-2
Original file line numberDiff line numberDiff line change
@@ -542,7 +542,7 @@ export function scheduleUpdateOnFiber(
542542

543543
if (enableProfilerTimer && enableProfilerNestedUpdateScheduledHook) {
544544
if (
545-
executionContext === CommitContext &&
545+
(executionContext & CommitContext) !== NoContext &&
546546
root === rootCommittingMutationOrLayoutEffects
547547
) {
548548
if (fiber.mode & ProfileMode) {
@@ -2240,7 +2240,7 @@ function commitRootImpl(root, renderPriorityLevel) {
22402240
}
22412241
}
22422242

2243-
if (remainingLanes === SyncLane) {
2243+
if (includesSomeLane(remainingLanes, (SyncLane: Lane))) {
22442244
if (enableProfilerTimer && enableProfilerNestedUpdatePhase) {
22452245
markNestedUpdateScheduled();
22462246
}

Diff for: packages/react-reconciler/src/ReactFiberWorkLoop.old.js

+2-2
Original file line numberDiff line numberDiff line change
@@ -542,7 +542,7 @@ export function scheduleUpdateOnFiber(
542542

543543
if (enableProfilerTimer && enableProfilerNestedUpdateScheduledHook) {
544544
if (
545-
executionContext === CommitContext &&
545+
(executionContext & CommitContext) !== NoContext &&
546546
root === rootCommittingMutationOrLayoutEffects
547547
) {
548548
if (fiber.mode & ProfileMode) {
@@ -2240,7 +2240,7 @@ function commitRootImpl(root, renderPriorityLevel) {
22402240
}
22412241
}
22422242

2243-
if (remainingLanes === SyncLane) {
2243+
if (includesSomeLane(remainingLanes, (SyncLane: Lane))) {
22442244
if (enableProfilerTimer && enableProfilerNestedUpdatePhase) {
22452245
markNestedUpdateScheduled();
22462246
}

Diff for: packages/react/src/__tests__/ReactProfiler-test.internal.js

+89-5
Original file line numberDiff line numberDiff line change
@@ -748,6 +748,52 @@ describe('Profiler', () => {
748748
expect(onRender.mock.calls[2][1]).toBe('update');
749749
});
750750

751+
it('is properly distinguish updates and nested-updates when there is more than sync remaining work', () => {
752+
loadModules({
753+
enableSchedulerTracing,
754+
useNoopRenderer: true,
755+
});
756+
757+
function Component() {
758+
const [didMount, setDidMount] = React.useState(false);
759+
760+
React.useLayoutEffect(() => {
761+
setDidMount(true);
762+
}, []);
763+
Scheduler.unstable_yieldValue(didMount);
764+
return didMount;
765+
}
766+
767+
const onRender = jest.fn();
768+
769+
// Schedule low-priority work.
770+
Scheduler.unstable_runWithPriority(
771+
Scheduler.unstable_LowPriority,
772+
() => {
773+
ReactNoop.render(
774+
<React.Profiler id="root" onRender={onRender}>
775+
<Component />
776+
</React.Profiler>,
777+
);
778+
},
779+
);
780+
781+
// Flush sync work with a nested upate
782+
ReactNoop.flushSync(() => {
783+
ReactNoop.render(
784+
<React.Profiler id="root" onRender={onRender}>
785+
<Component />
786+
</React.Profiler>,
787+
);
788+
});
789+
expect(Scheduler).toHaveYielded([false, true]);
790+
791+
// Verify that the nested update inside of the sync work is appropriately tagged.
792+
expect(onRender).toHaveBeenCalledTimes(2);
793+
expect(onRender.mock.calls[0][1]).toBe('mount');
794+
expect(onRender.mock.calls[1][1]).toBe('nested-update');
795+
});
796+
751797
describe('with regard to interruptions', () => {
752798
it('should accumulate actual time after a scheduling interruptions', () => {
753799
const callback = jest.fn();
@@ -2582,14 +2628,50 @@ describe('Profiler', () => {
25822628

25832629
expect(Scheduler).toHaveYielded(['Component:false', 'Component:true']);
25842630
expect(onNestedUpdateScheduled).toHaveBeenCalledTimes(1);
2631+
expect(onNestedUpdateScheduled.mock.calls[0][0]).toBe('test');
25852632
if (ReactFeatureFlags.enableSchedulerTracing) {
2586-
expect(onNestedUpdateScheduled.mock.calls[0][0]).toBe('test');
25872633
expect(onNestedUpdateScheduled.mock.calls[0][1]).toMatchInteractions([
25882634
interactionCreation,
25892635
]);
25902636
}
25912637
});
25922638

2639+
it('is called when a function component schedules a batched update during a layout effect', () => {
2640+
function Component() {
2641+
const [didMount, setDidMount] = React.useState(false);
2642+
React.useLayoutEffect(() => {
2643+
ReactNoop.batchedUpdates(() => {
2644+
setDidMount(true);
2645+
});
2646+
}, []);
2647+
Scheduler.unstable_yieldValue(`Component:${didMount}`);
2648+
return didMount;
2649+
}
2650+
2651+
const onNestedUpdateScheduled = jest.fn();
2652+
const onRender = jest.fn();
2653+
2654+
ReactNoop.render(
2655+
<React.Profiler
2656+
id="root"
2657+
onNestedUpdateScheduled={onNestedUpdateScheduled}
2658+
onRender={onRender}>
2659+
<Component />
2660+
</React.Profiler>,
2661+
);
2662+
expect(Scheduler).toFlushAndYield([
2663+
'Component:false',
2664+
'Component:true',
2665+
]);
2666+
2667+
expect(onRender).toHaveBeenCalledTimes(2);
2668+
expect(onRender.mock.calls[0][1]).toBe('mount');
2669+
expect(onRender.mock.calls[1][1]).toBe('nested-update');
2670+
2671+
expect(onNestedUpdateScheduled).toHaveBeenCalledTimes(1);
2672+
expect(onNestedUpdateScheduled.mock.calls[0][0]).toBe('root');
2673+
});
2674+
25932675
it('bubbles up and calls all ancestor Profilers', () => {
25942676
function Component() {
25952677
const [didMount, setDidMount] = React.useState(false);
@@ -2819,8 +2901,8 @@ describe('Profiler', () => {
28192901
'Component:true:false',
28202902
]);
28212903
expect(onNestedUpdateScheduled).toHaveBeenCalledTimes(1);
2904+
expect(onNestedUpdateScheduled.mock.calls[0][0]).toBe('test');
28222905
if (ReactFeatureFlags.enableSchedulerTracing) {
2823-
expect(onNestedUpdateScheduled.mock.calls[0][0]).toBe('test');
28242906
expect(onNestedUpdateScheduled.mock.calls[0][1]).toMatchInteractions([
28252907
interactionCreation,
28262908
]);
@@ -2853,8 +2935,8 @@ describe('Profiler', () => {
28532935
'Component:true:true',
28542936
]);
28552937
expect(onNestedUpdateScheduled).toHaveBeenCalledTimes(2);
2938+
expect(onNestedUpdateScheduled.mock.calls[1][0]).toBe('test');
28562939
if (ReactFeatureFlags.enableSchedulerTracing) {
2857-
expect(onNestedUpdateScheduled.mock.calls[1][0]).toBe('test');
28582940
expect(onNestedUpdateScheduled.mock.calls[1][1]).toMatchInteractions([
28592941
interactionUpdate,
28602942
]);
@@ -2902,8 +2984,8 @@ describe('Profiler', () => {
29022984

29032985
expect(Scheduler).toHaveYielded(['Component:false', 'Component:true']);
29042986
expect(onNestedUpdateScheduled).toHaveBeenCalledTimes(1);
2987+
expect(onNestedUpdateScheduled.mock.calls[0][0]).toBe('test');
29052988
if (ReactFeatureFlags.enableSchedulerTracing) {
2906-
expect(onNestedUpdateScheduled.mock.calls[0][0]).toBe('test');
29072989
expect(onNestedUpdateScheduled.mock.calls[0][1]).toMatchInteractions([
29082990
interactionCreation,
29092991
]);
@@ -2974,8 +3056,8 @@ describe('Profiler', () => {
29743056
'Component:true:true',
29753057
]);
29763058
expect(onNestedUpdateScheduled).toHaveBeenCalledTimes(1);
3059+
expect(onNestedUpdateScheduled.mock.calls[0][0]).toBe('test');
29773060
if (ReactFeatureFlags.enableSchedulerTracing) {
2978-
expect(onNestedUpdateScheduled.mock.calls[0][0]).toBe('test');
29793061
expect(onNestedUpdateScheduled.mock.calls[0][1]).toMatchInteractions([
29803062
interactionCreation,
29813063
]);
@@ -3016,6 +3098,8 @@ describe('Profiler', () => {
30163098
expect(Scheduler).toHaveYielded(['Component:true']);
30173099
expect(onNestedUpdateScheduled).not.toHaveBeenCalled();
30183100
});
3101+
3102+
// TODO Add hydration tests to ensure we don't have false positives called.
30193103
});
30203104
});
30213105

0 commit comments

Comments
 (0)