Skip to content

Commit dc108b0

Browse files
author
Brian Vaughn
authored
Track which fibers scheduled the current render work (#15658)
Tracked Fibers are called "updaters" and are exposed to DevTools via a 'memoizedUpdaters' property on the ReactFiberRoot. The implementation of this feature follows a vaguely similar approach as interaction tracing, but does not require reference counting since there is no subscriptions API. This change is in support of a new DevTools Profiler feature that shows which Fiber(s) scheduled the selected commit in the Profiler. All changes have been gated behind a new feature flag, 'enableUpdaterTracking', which is enabled for Profiling builds by default. We also only track updaters when DevTools has been detected, to avoid doing unnecessary work.
1 parent 6ea7491 commit dc108b0

22 files changed

Lines changed: 912 additions & 10 deletions

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

Lines changed: 38 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -37,6 +37,7 @@ import {
3737
enableStrictEffects,
3838
deletedTreeCleanUpLevel,
3939
enableSuspenseLayoutEffectSemantics,
40+
enableUpdaterTracking,
4041
}from'shared/ReactFeatureFlags';
4142
import{
4243
FunctionComponent,
@@ -89,7 +90,7 @@ import {
8990
resetCurrentFiberasresetCurrentDebugFiberInDEV,
9091
setCurrentFiberassetCurrentDebugFiberInDEV,
9192
}from'./ReactCurrentFiber';
92-
93+
import{isDevToolsPresent}from'./ReactFiberDevToolsHook.new';
9394
import{onCommitUnmount}from'./ReactFiberDevToolsHook.new';
9495
import{resolveDefaultProps}from'./ReactFiberLazyComponent.new';
9596
import{
@@ -137,6 +138,7 @@ import {
137138
resolveRetryWakeable,
138139
markCommitTimeOfFallback,
139140
enqueuePendingPassiveProfilerEffect,
141+
restorePendingUpdaters,
140142
}from'./ReactFiberWorkLoop.new';
141143
import{
142144
NoFlagsasNoHookEffect,
@@ -162,6 +164,10 @@ const PossiblyWeakSet = typeof WeakSet === 'function' ? WeakSet : Set;
162164

163165
letnextEffect: Fiber|null=null;
164166

167+
// Used for Profiling builds to track updaters.
168+
letinProgressLanes: Lanes|null=null;
169+
letinProgressRoot: FiberRoot|null=null;
170+
165171
constcallComponentWillUnmountWithTimer=function(current,instance){
166172
instance.props=current.memoizedProps;
167173
instance.state=current.memoizedState;
@@ -2094,6 +2100,20 @@ function attachSuspenseRetryListeners(finishedWork: Fiber) {
20942100
}
20952101
}
20962102
retryCache.add(wakeable);
2103+
2104+
if(enableUpdaterTracking){
2105+
if(isDevToolsPresent){
2106+
if(inProgressLanes!==null&&inProgressRoot!==null){
2107+
// If we have pending work still, associate the original updaters with it.
2108+
restorePendingUpdaters(inProgressRoot,inProgressLanes);
2109+
}else{
2110+
throwError(
2111+
'Expected finished root and lanes to be set. This is a bug in React.',
2112+
);
2113+
}
2114+
}
2115+
}
2116+
20972117
wakeable.then(retry,retry);
20982118
}
20992119
});
@@ -2124,9 +2144,19 @@ function commitResetTextContent(current: Fiber) {
21242144
resetTextContent(current.stateNode);
21252145
}
21262146

2127-
exportfunctioncommitMutationEffects(root: FiberRoot,firstChild: Fiber){
2147+
exportfunctioncommitMutationEffects(
2148+
root: FiberRoot,
2149+
firstChild: Fiber,
2150+
committedLanes: Lanes,
2151+
){
2152+
inProgressLanes=committedLanes;
2153+
inProgressRoot=root;
21282154
nextEffect=firstChild;
2155+
21292156
commitMutationEffects_begin(root);
2157+
2158+
inProgressLanes=null;
2159+
inProgressRoot=null;
21302160
}
21312161

21322162
functioncommitMutationEffects_begin(root: FiberRoot){
@@ -2280,8 +2310,14 @@ export function commitLayoutEffects(
22802310
root: FiberRoot,
22812311
committedLanes: Lanes,
22822312
): void{
2313+
inProgressLanes=committedLanes;
2314+
inProgressRoot=root;
22832315
nextEffect=finishedWork;
2316+
22842317
commitLayoutEffects_begin(finishedWork,root,committedLanes);
2318+
2319+
inProgressLanes=null;
2320+
inProgressRoot=null;
22852321
}
22862322

22872323
functioncommitLayoutEffects_begin(

‎packages/react-reconciler/src/ReactFiberCommitWork.old.js‎

Lines changed: 38 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -37,6 +37,7 @@ import {
3737
enableStrictEffects,
3838
deletedTreeCleanUpLevel,
3939
enableSuspenseLayoutEffectSemantics,
40+
enableUpdaterTracking,
4041
}from'shared/ReactFeatureFlags';
4142
import{
4243
FunctionComponent,
@@ -89,7 +90,7 @@ import {
8990
resetCurrentFiberasresetCurrentDebugFiberInDEV,
9091
setCurrentFiberassetCurrentDebugFiberInDEV,
9192
}from'./ReactCurrentFiber';
92-
93+
import{isDevToolsPresent}from'./ReactFiberDevToolsHook.old';
9394
import{onCommitUnmount}from'./ReactFiberDevToolsHook.old';
9495
import{resolveDefaultProps}from'./ReactFiberLazyComponent.old';
9596
import{
@@ -137,6 +138,7 @@ import {
137138
resolveRetryWakeable,
138139
markCommitTimeOfFallback,
139140
enqueuePendingPassiveProfilerEffect,
141+
restorePendingUpdaters,
140142
}from'./ReactFiberWorkLoop.old';
141143
import{
142144
NoFlagsasNoHookEffect,
@@ -162,6 +164,10 @@ const PossiblyWeakSet = typeof WeakSet === 'function' ? WeakSet : Set;
162164

163165
letnextEffect: Fiber|null=null;
164166

167+
// Used for Profiling builds to track updaters.
168+
letinProgressLanes: Lanes|null=null;
169+
letinProgressRoot: FiberRoot|null=null;
170+
165171
constcallComponentWillUnmountWithTimer=function(current,instance){
166172
instance.props=current.memoizedProps;
167173
instance.state=current.memoizedState;
@@ -2094,6 +2100,20 @@ function attachSuspenseRetryListeners(finishedWork: Fiber) {
20942100
}
20952101
}
20962102
retryCache.add(wakeable);
2103+
2104+
if(enableUpdaterTracking){
2105+
if(isDevToolsPresent){
2106+
if(inProgressLanes!==null&&inProgressRoot!==null){
2107+
// If we have pending work still, associate the original updaters with it.
2108+
restorePendingUpdaters(inProgressRoot,inProgressLanes);
2109+
}else{
2110+
throwError(
2111+
'Expected finished root and lanes to be set. This is a bug in React.',
2112+
);
2113+
}
2114+
}
2115+
}
2116+
20972117
wakeable.then(retry,retry);
20982118
}
20992119
});
@@ -2124,9 +2144,19 @@ function commitResetTextContent(current: Fiber) {
21242144
resetTextContent(current.stateNode);
21252145
}
21262146

2127-
exportfunctioncommitMutationEffects(root: FiberRoot,firstChild: Fiber){
2147+
exportfunctioncommitMutationEffects(
2148+
root: FiberRoot,
2149+
firstChild: Fiber,
2150+
committedLanes: Lanes,
2151+
){
2152+
inProgressLanes=committedLanes;
2153+
inProgressRoot=root;
21282154
nextEffect=firstChild;
2155+
21292156
commitMutationEffects_begin(root);
2157+
2158+
inProgressLanes=null;
2159+
inProgressRoot=null;
21302160
}
21312161

21322162
functioncommitMutationEffects_begin(root: FiberRoot){
@@ -2280,8 +2310,14 @@ export function commitLayoutEffects(
22802310
root: FiberRoot,
22812311
committedLanes: Lanes,
22822312
): void{
2313+
inProgressLanes=committedLanes;
2314+
inProgressRoot=root;
22832315
nextEffect=finishedWork;
2316+
22842317
commitLayoutEffects_begin(finishedWork,root,committedLanes);
2318+
2319+
inProgressLanes=null;
2320+
inProgressRoot=null;
22852321
}
22862322

22872323
functioncommitLayoutEffects_begin(

‎packages/react-reconciler/src/ReactFiberLane.new.js‎

Lines changed: 57 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -35,7 +35,12 @@ export type Lanes = number;
3535
exporttypeLane=number;
3636
exporttypeLaneMap<T>=Array<T>;
3737

38-
import{enableCache,enableSchedulingProfiler}from'shared/ReactFeatureFlags';
38+
import{
39+
enableCache,
40+
enableSchedulingProfiler,
41+
enableUpdaterTracking,
42+
}from'shared/ReactFeatureFlags';
43+
import{isDevToolsPresent}from'./ReactFiberDevToolsHook.new';
3944

4045
// Lane values below should be kept in sync with getLabelsForLanes(), used by react-devtools-scheduling-profiler.
4146
// If those values are changed that package should be rebuilt and redeployed.
@@ -742,6 +747,57 @@ export function getBumpedLaneForHydration(
742747
returnlane;
743748
}
744749

750+
exportfunctionaddFiberToLanesMap(
751+
root: FiberRoot,
752+
fiber: Fiber,
753+
lanes: Lanes|Lane,
754+
){
755+
if(!enableUpdaterTracking){
756+
return;
757+
}
758+
if(!isDevToolsPresent){
759+
return;
760+
}
761+
constpendingUpdatersLaneMap=root.pendingUpdatersLaneMap;
762+
while(lanes>0){
763+
constindex=laneToIndex(lanes);
764+
constlane=1<<index;
765+
766+
constupdaters=pendingUpdatersLaneMap[index];
767+
updaters.add(fiber);
768+
769+
lanes&=~lane;
770+
}
771+
}
772+
773+
exportfunctionmovePendingFibersToMemoized(root: FiberRoot,lanes: Lanes){
774+
if(!enableUpdaterTracking){
775+
return;
776+
}
777+
if(!isDevToolsPresent){
778+
return;
779+
}
780+
constpendingUpdatersLaneMap=root.pendingUpdatersLaneMap;
781+
constmemoizedUpdaters=root.memoizedUpdaters;
782+
while(lanes>0){
783+
constindex=laneToIndex(lanes);
784+
constlane=1<<index;
785+
786+
constupdaters=pendingUpdatersLaneMap[index];
787+
if(updaters.size>0){
788+
updaters.forEach(fiber=>{
789+
constalternate=fiber.alternate;
790+
if(alternate===null||!memoizedUpdaters.has(alternate)){
791+
memoizedUpdaters.add(fiber);
792+
}
793+
});
794+
updaters.clear();
795+
}
796+
797+
lanes&=~lane;
798+
}
799+
}
800+
745801
constclz32=Math.clz32 ? Math.clz32 : clz32Fallback;
746802

747803
// Count leading zeros. Only used on lanes, so assume input is an integer.

‎packages/react-reconciler/src/ReactFiberLane.old.js‎

Lines changed: 57 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -35,7 +35,12 @@ export type Lanes = number;
3535
exporttypeLane=number;
3636
exporttypeLaneMap<T>=Array<T>;
3737

38-
import{enableCache,enableSchedulingProfiler}from'shared/ReactFeatureFlags';
38+
import{
39+
enableCache,
40+
enableSchedulingProfiler,
41+
enableUpdaterTracking,
42+
}from'shared/ReactFeatureFlags';
43+
import{isDevToolsPresent}from'./ReactFiberDevToolsHook.old';
3944

4045
// Lane values below should be kept in sync with getLabelsForLanes(), used by react-devtools-scheduling-profiler.
4146
// If those values are changed that package should be rebuilt and redeployed.
@@ -742,6 +747,57 @@ export function getBumpedLaneForHydration(
742747
returnlane;
743748
}
744749

750+
exportfunctionaddFiberToLanesMap(
751+
root: FiberRoot,
752+
fiber: Fiber,
753+
lanes: Lanes|Lane,
754+
){
755+
if(!enableUpdaterTracking){
756+
return;
757+
}
758+
if(!isDevToolsPresent){
759+
return;
760+
}
761+
constpendingUpdatersLaneMap=root.pendingUpdatersLaneMap;
762+
while(lanes>0){
763+
constindex=laneToIndex(lanes);
764+
constlane=1<<index;
765+
766+
constupdaters=pendingUpdatersLaneMap[index];
767+
updaters.add(fiber);
768+
769+
lanes&=~lane;
770+
}
771+
}
772+
773+
exportfunctionmovePendingFibersToMemoized(root: FiberRoot,lanes: Lanes){
774+
if(!enableUpdaterTracking){
775+
return;
776+
}
777+
if(!isDevToolsPresent){
778+
return;
779+
}
780+
constpendingUpdatersLaneMap=root.pendingUpdatersLaneMap;
781+
constmemoizedUpdaters=root.memoizedUpdaters;
782+
while(lanes>0){
783+
constindex=laneToIndex(lanes);
784+
constlane=1<<index;
785+
786+
constupdaters=pendingUpdatersLaneMap[index];
787+
if(updaters.size>0){
788+
updaters.forEach(fiber=>{
789+
constalternate=fiber.alternate;
790+
if(alternate===null||!memoizedUpdaters.has(alternate)){
791+
memoizedUpdaters.add(fiber);
792+
}
793+
});
794+
updaters.clear();
795+
}
796+
797+
lanes&=~lane;
798+
}
799+
}
800+
745801
constclz32=Math.clz32 ? Math.clz32 : clz32Fallback;
746802

747803
// Count leading zeros. Only used on lanes, so assume input is an integer.

‎packages/react-reconciler/src/ReactFiberRoot.new.js‎

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@ import {
1616
NoLane,
1717
NoLanes,
1818
NoTimestamp,
19+
TotalLanes,
1920
createLaneMap,
2021
}from'./ReactFiberLane.new';
2122
import{
@@ -24,6 +25,7 @@ import {
2425
enableCache,
2526
enableProfilerCommitHooks,
2627
enableProfilerTimer,
28+
enableUpdaterTracking,
2729
}from'shared/ReactFeatureFlags';
2830
import{unstable_getThreadID}from'scheduler/tracing';
2931
import{initializeUpdateQueue}from'./ReactUpdateQueue.new';
@@ -77,6 +79,14 @@ function FiberRootNode(containerInfo, tag, hydrate) {
7779
this.passiveEffectDuration=0;
7880
}
7981

82+
if(enableUpdaterTracking){
83+
this.memoizedUpdaters=newSet();
84+
constpendingUpdatersLaneMap=(this.pendingUpdatersLaneMap=[]);
85+
for(leti=0;i<TotalLanes;i++){
86+
pendingUpdatersLaneMap.push(newSet());
87+
}
88+
}
89+
8090
if(__DEV__){
8191
switch(tag){
8292
caseConcurrentRoot:

‎packages/react-reconciler/src/ReactFiberRoot.old.js‎

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@ import {
1616
NoLane,
1717
NoLanes,
1818
NoTimestamp,
19+
TotalLanes,
1920
createLaneMap,
2021
}from'./ReactFiberLane.old';
2122
import{
@@ -24,6 +25,7 @@ import {
2425
enableCache,
2526
enableProfilerCommitHooks,
2627
enableProfilerTimer,
28+
enableUpdaterTracking,
2729
}from'shared/ReactFeatureFlags';
2830
import{unstable_getThreadID}from'scheduler/tracing';
2931
import{initializeUpdateQueue}from'./ReactUpdateQueue.old';
@@ -77,6 +79,14 @@ function FiberRootNode(containerInfo, tag, hydrate) {
7779
this.passiveEffectDuration=0;
7880
}
7981

82+
if(enableUpdaterTracking){
83+
this.memoizedUpdaters=newSet();
84+
constpendingUpdatersLaneMap=(this.pendingUpdatersLaneMap=[]);
85+
for(leti=0;i<TotalLanes;i++){
86+
pendingUpdatersLaneMap.push(newSet());
87+
}
88+
}
89+
8090
if(__DEV__){
8191
switch(tag){
8292
caseConcurrentRoot:

0 commit comments

Comments
 (0)