Skip to content

Commit

Permalink
Track which fibers scheduled the current render work (#15658)
Browse files Browse the repository at this point in the history
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.
  • Loading branch information
Brian Vaughn authored Apr 9, 2021
1 parent 6ea7491 commit dc108b0
Show file tree
Hide file tree
Showing 22 changed files with 912 additions and 10 deletions.
40 changes: 38 additions & 2 deletions packages/react-reconciler/src/ReactFiberCommitWork.new.js
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,7 @@ import {
enableStrictEffects,
deletedTreeCleanUpLevel,
enableSuspenseLayoutEffectSemantics,
enableUpdaterTracking,
} from 'shared/ReactFeatureFlags';
import {
FunctionComponent,
Expand Down Expand Up @@ -89,7 +90,7 @@ import {
resetCurrentFiber as resetCurrentDebugFiberInDEV,
setCurrentFiber as setCurrentDebugFiberInDEV,
} from './ReactCurrentFiber';

import {isDevToolsPresent} from './ReactFiberDevToolsHook.new';
import {onCommitUnmount} from './ReactFiberDevToolsHook.new';
import {resolveDefaultProps} from './ReactFiberLazyComponent.new';
import {
Expand Down Expand Up @@ -137,6 +138,7 @@ import {
resolveRetryWakeable,
markCommitTimeOfFallback,
enqueuePendingPassiveProfilerEffect,
restorePendingUpdaters,
} from './ReactFiberWorkLoop.new';
import {
NoFlags as NoHookEffect,
Expand All @@ -162,6 +164,10 @@ const PossiblyWeakSet = typeof WeakSet === 'function' ? WeakSet : Set;

let nextEffect: Fiber | null = null;

// Used for Profiling builds to track updaters.
let inProgressLanes: Lanes | null = null;
let inProgressRoot: FiberRoot | null = null;

const callComponentWillUnmountWithTimer = function(current, instance) {
instance.props = current.memoizedProps;
instance.state = current.memoizedState;
Expand Down Expand Up @@ -2094,6 +2100,20 @@ function attachSuspenseRetryListeners(finishedWork: Fiber) {
}
}
retryCache.add(wakeable);

if (enableUpdaterTracking) {
if (isDevToolsPresent) {
if (inProgressLanes !== null && inProgressRoot !== null) {
// If we have pending work still, associate the original updaters with it.
restorePendingUpdaters(inProgressRoot, inProgressLanes);
} else {
throw Error(
'Expected finished root and lanes to be set. This is a bug in React.',
);
}
}
}

wakeable.then(retry, retry);
}
});
Expand Down Expand Up @@ -2124,9 +2144,19 @@ function commitResetTextContent(current: Fiber) {
resetTextContent(current.stateNode);
}

export function commitMutationEffects(root: FiberRoot, firstChild: Fiber) {
export function commitMutationEffects(
root: FiberRoot,
firstChild: Fiber,
committedLanes: Lanes,
) {
inProgressLanes = committedLanes;
inProgressRoot = root;
nextEffect = firstChild;

commitMutationEffects_begin(root);

inProgressLanes = null;
inProgressRoot = null;
}

function commitMutationEffects_begin(root: FiberRoot) {
Expand Down Expand Up @@ -2280,8 +2310,14 @@ export function commitLayoutEffects(
root: FiberRoot,
committedLanes: Lanes,
): void {
inProgressLanes = committedLanes;
inProgressRoot = root;
nextEffect = finishedWork;

commitLayoutEffects_begin(finishedWork, root, committedLanes);

inProgressLanes = null;
inProgressRoot = null;
}

function commitLayoutEffects_begin(
Expand Down
40 changes: 38 additions & 2 deletions packages/react-reconciler/src/ReactFiberCommitWork.old.js
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,7 @@ import {
enableStrictEffects,
deletedTreeCleanUpLevel,
enableSuspenseLayoutEffectSemantics,
enableUpdaterTracking,
} from 'shared/ReactFeatureFlags';
import {
FunctionComponent,
Expand Down Expand Up @@ -89,7 +90,7 @@ import {
resetCurrentFiber as resetCurrentDebugFiberInDEV,
setCurrentFiber as setCurrentDebugFiberInDEV,
} from './ReactCurrentFiber';

import {isDevToolsPresent} from './ReactFiberDevToolsHook.old';
import {onCommitUnmount} from './ReactFiberDevToolsHook.old';
import {resolveDefaultProps} from './ReactFiberLazyComponent.old';
import {
Expand Down Expand Up @@ -137,6 +138,7 @@ import {
resolveRetryWakeable,
markCommitTimeOfFallback,
enqueuePendingPassiveProfilerEffect,
restorePendingUpdaters,
} from './ReactFiberWorkLoop.old';
import {
NoFlags as NoHookEffect,
Expand All @@ -162,6 +164,10 @@ const PossiblyWeakSet = typeof WeakSet === 'function' ? WeakSet : Set;

let nextEffect: Fiber | null = null;

// Used for Profiling builds to track updaters.
let inProgressLanes: Lanes | null = null;
let inProgressRoot: FiberRoot | null = null;

const callComponentWillUnmountWithTimer = function(current, instance) {
instance.props = current.memoizedProps;
instance.state = current.memoizedState;
Expand Down Expand Up @@ -2094,6 +2100,20 @@ function attachSuspenseRetryListeners(finishedWork: Fiber) {
}
}
retryCache.add(wakeable);

if (enableUpdaterTracking) {
if (isDevToolsPresent) {
if (inProgressLanes !== null && inProgressRoot !== null) {
// If we have pending work still, associate the original updaters with it.
restorePendingUpdaters(inProgressRoot, inProgressLanes);
} else {
throw Error(
'Expected finished root and lanes to be set. This is a bug in React.',
);
}
}
}

wakeable.then(retry, retry);
}
});
Expand Down Expand Up @@ -2124,9 +2144,19 @@ function commitResetTextContent(current: Fiber) {
resetTextContent(current.stateNode);
}

export function commitMutationEffects(root: FiberRoot, firstChild: Fiber) {
export function commitMutationEffects(
root: FiberRoot,
firstChild: Fiber,
committedLanes: Lanes,
) {
inProgressLanes = committedLanes;
inProgressRoot = root;
nextEffect = firstChild;

commitMutationEffects_begin(root);

inProgressLanes = null;
inProgressRoot = null;
}

function commitMutationEffects_begin(root: FiberRoot) {
Expand Down Expand Up @@ -2280,8 +2310,14 @@ export function commitLayoutEffects(
root: FiberRoot,
committedLanes: Lanes,
): void {
inProgressLanes = committedLanes;
inProgressRoot = root;
nextEffect = finishedWork;

commitLayoutEffects_begin(finishedWork, root, committedLanes);

inProgressLanes = null;
inProgressRoot = null;
}

function commitLayoutEffects_begin(
Expand Down
58 changes: 57 additions & 1 deletion packages/react-reconciler/src/ReactFiberLane.new.js
Original file line number Diff line number Diff line change
Expand Up @@ -35,7 +35,12 @@ export type Lanes = number;
export type Lane = number;
export type LaneMap<T> = Array<T>;

import {enableCache, enableSchedulingProfiler} from 'shared/ReactFeatureFlags';
import {
enableCache,
enableSchedulingProfiler,
enableUpdaterTracking,
} from 'shared/ReactFeatureFlags';
import {isDevToolsPresent} from './ReactFiberDevToolsHook.new';

// Lane values below should be kept in sync with getLabelsForLanes(), used by react-devtools-scheduling-profiler.
// If those values are changed that package should be rebuilt and redeployed.
Expand Down Expand Up @@ -742,6 +747,57 @@ export function getBumpedLaneForHydration(
return lane;
}

export function addFiberToLanesMap(
root: FiberRoot,
fiber: Fiber,
lanes: Lanes | Lane,
) {
if (!enableUpdaterTracking) {
return;
}
if (!isDevToolsPresent) {
return;
}
const pendingUpdatersLaneMap = root.pendingUpdatersLaneMap;
while (lanes > 0) {
const index = laneToIndex(lanes);
const lane = 1 << index;

const updaters = pendingUpdatersLaneMap[index];
updaters.add(fiber);

lanes &= ~lane;
}
}

export function movePendingFibersToMemoized(root: FiberRoot, lanes: Lanes) {
if (!enableUpdaterTracking) {
return;
}
if (!isDevToolsPresent) {
return;
}
const pendingUpdatersLaneMap = root.pendingUpdatersLaneMap;
const memoizedUpdaters = root.memoizedUpdaters;
while (lanes > 0) {
const index = laneToIndex(lanes);
const lane = 1 << index;

const updaters = pendingUpdatersLaneMap[index];
if (updaters.size > 0) {
updaters.forEach(fiber => {
const alternate = fiber.alternate;
if (alternate === null || !memoizedUpdaters.has(alternate)) {
memoizedUpdaters.add(fiber);
}
});
updaters.clear();
}

lanes &= ~lane;
}
}

const clz32 = Math.clz32 ? Math.clz32 : clz32Fallback;

// Count leading zeros. Only used on lanes, so assume input is an integer.
Expand Down
58 changes: 57 additions & 1 deletion packages/react-reconciler/src/ReactFiberLane.old.js
Original file line number Diff line number Diff line change
Expand Up @@ -35,7 +35,12 @@ export type Lanes = number;
export type Lane = number;
export type LaneMap<T> = Array<T>;

import {enableCache, enableSchedulingProfiler} from 'shared/ReactFeatureFlags';
import {
enableCache,
enableSchedulingProfiler,
enableUpdaterTracking,
} from 'shared/ReactFeatureFlags';
import {isDevToolsPresent} from './ReactFiberDevToolsHook.old';

// Lane values below should be kept in sync with getLabelsForLanes(), used by react-devtools-scheduling-profiler.
// If those values are changed that package should be rebuilt and redeployed.
Expand Down Expand Up @@ -742,6 +747,57 @@ export function getBumpedLaneForHydration(
return lane;
}

export function addFiberToLanesMap(
root: FiberRoot,
fiber: Fiber,
lanes: Lanes | Lane,
) {
if (!enableUpdaterTracking) {
return;
}
if (!isDevToolsPresent) {
return;
}
const pendingUpdatersLaneMap = root.pendingUpdatersLaneMap;
while (lanes > 0) {
const index = laneToIndex(lanes);
const lane = 1 << index;

const updaters = pendingUpdatersLaneMap[index];
updaters.add(fiber);

lanes &= ~lane;
}
}

export function movePendingFibersToMemoized(root: FiberRoot, lanes: Lanes) {
if (!enableUpdaterTracking) {
return;
}
if (!isDevToolsPresent) {
return;
}
const pendingUpdatersLaneMap = root.pendingUpdatersLaneMap;
const memoizedUpdaters = root.memoizedUpdaters;
while (lanes > 0) {
const index = laneToIndex(lanes);
const lane = 1 << index;

const updaters = pendingUpdatersLaneMap[index];
if (updaters.size > 0) {
updaters.forEach(fiber => {
const alternate = fiber.alternate;
if (alternate === null || !memoizedUpdaters.has(alternate)) {
memoizedUpdaters.add(fiber);
}
});
updaters.clear();
}

lanes &= ~lane;
}
}

const clz32 = Math.clz32 ? Math.clz32 : clz32Fallback;

// Count leading zeros. Only used on lanes, so assume input is an integer.
Expand Down
10 changes: 10 additions & 0 deletions packages/react-reconciler/src/ReactFiberRoot.new.js
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ import {
NoLane,
NoLanes,
NoTimestamp,
TotalLanes,
createLaneMap,
} from './ReactFiberLane.new';
import {
Expand All @@ -24,6 +25,7 @@ import {
enableCache,
enableProfilerCommitHooks,
enableProfilerTimer,
enableUpdaterTracking,
} from 'shared/ReactFeatureFlags';
import {unstable_getThreadID} from 'scheduler/tracing';
import {initializeUpdateQueue} from './ReactUpdateQueue.new';
Expand Down Expand Up @@ -77,6 +79,14 @@ function FiberRootNode(containerInfo, tag, hydrate) {
this.passiveEffectDuration = 0;
}

if (enableUpdaterTracking) {
this.memoizedUpdaters = new Set();
const pendingUpdatersLaneMap = (this.pendingUpdatersLaneMap = []);
for (let i = 0; i < TotalLanes; i++) {
pendingUpdatersLaneMap.push(new Set());
}
}

if (__DEV__) {
switch (tag) {
case ConcurrentRoot:
Expand Down
10 changes: 10 additions & 0 deletions packages/react-reconciler/src/ReactFiberRoot.old.js
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ import {
NoLane,
NoLanes,
NoTimestamp,
TotalLanes,
createLaneMap,
} from './ReactFiberLane.old';
import {
Expand All @@ -24,6 +25,7 @@ import {
enableCache,
enableProfilerCommitHooks,
enableProfilerTimer,
enableUpdaterTracking,
} from 'shared/ReactFeatureFlags';
import {unstable_getThreadID} from 'scheduler/tracing';
import {initializeUpdateQueue} from './ReactUpdateQueue.old';
Expand Down Expand Up @@ -77,6 +79,14 @@ function FiberRootNode(containerInfo, tag, hydrate) {
this.passiveEffectDuration = 0;
}

if (enableUpdaterTracking) {
this.memoizedUpdaters = new Set();
const pendingUpdatersLaneMap = (this.pendingUpdatersLaneMap = []);
for (let i = 0; i < TotalLanes; i++) {
pendingUpdatersLaneMap.push(new Set());
}
}

if (__DEV__) {
switch (tag) {
case ConcurrentRoot:
Expand Down
Loading

0 comments on commit dc108b0

Please sign in to comment.