Skip to content

Commit 6145677

Browse files
author
Brian Vaughn
committed
Track which fibers scheduled the current render work
1 parent 1160b37 commit 6145677

12 files changed

+673
-44
lines changed

packages/react-reconciler/src/ReactFiberCommitWork.js

Lines changed: 34 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,7 @@ import {
2828
enableSchedulerTracing,
2929
enableProfilerTimer,
3030
enableSuspenseServerRenderer,
31+
enableUpdaterTracking,
3132
enableEventAPI,
3233
} from 'shared/ReactFeatureFlags';
3334
import {
@@ -104,6 +105,7 @@ import {
104105
captureCommitPhaseError,
105106
requestCurrentTime,
106107
resolveRetryThenable,
108+
restorePendingUpdaters,
107109
} from './ReactFiberScheduler';
108110
import {
109111
NoEffect as NoHookEffect,
@@ -1169,7 +1171,12 @@ function commitDeletion(current: Fiber): void {
11691171
detachFiber(current);
11701172
}
11711173

1172-
function commitWork(current: Fiber | null, finishedWork: Fiber): void {
1174+
function commitWork(
1175+
finishedRoot: FiberRoot,
1176+
current: Fiber | null,
1177+
finishedWork: Fiber,
1178+
committedExpirationTime: ExpirationTime,
1179+
): void {
11731180
if (!supportsMutation) {
11741181
switch (finishedWork.tag) {
11751182
case FunctionComponent:
@@ -1185,7 +1192,11 @@ function commitWork(current: Fiber | null, finishedWork: Fiber): void {
11851192
return;
11861193
}
11871194
case SuspenseComponent: {
1188-
commitSuspenseComponent(finishedWork);
1195+
commitSuspenseComponent(
1196+
finishedRoot,
1197+
finishedWork,
1198+
committedExpirationTime,
1199+
);
11891200
return;
11901201
}
11911202
}
@@ -1259,7 +1270,11 @@ function commitWork(current: Fiber | null, finishedWork: Fiber): void {
12591270
return;
12601271
}
12611272
case SuspenseComponent: {
1262-
commitSuspenseComponent(finishedWork);
1273+
commitSuspenseComponent(
1274+
finishedRoot,
1275+
finishedWork,
1276+
committedExpirationTime,
1277+
);
12631278
return;
12641279
}
12651280
case IncompleteClassComponent: {
@@ -1278,7 +1293,11 @@ function commitWork(current: Fiber | null, finishedWork: Fiber): void {
12781293
}
12791294
}
12801295

1281-
function commitSuspenseComponent(finishedWork: Fiber) {
1296+
function commitSuspenseComponent(
1297+
finishedRoot: FiberRoot,
1298+
finishedWork: Fiber,
1299+
committedExpirationTime: ExpirationTime,
1300+
) {
12821301
let newState: SuspenseState | null = finishedWork.memoizedState;
12831302

12841303
let newDidTimeout;
@@ -1316,11 +1335,21 @@ function commitSuspenseComponent(finishedWork: Fiber) {
13161335
}
13171336
thenables.forEach(thenable => {
13181337
// Memoize using the boundary fiber to prevent redundant listeners.
1319-
let retry = resolveRetryThenable.bind(null, finishedWork, thenable);
1338+
let retry = resolveRetryThenable.bind(
1339+
null,
1340+
finishedWork,
1341+
thenable,
1342+
finishedRoot,
1343+
committedExpirationTime,
1344+
);
13201345
if (!retryCache.has(thenable)) {
13211346
if (enableSchedulerTracing) {
13221347
retry = Schedule_tracing_wrap(retry);
13231348
}
1349+
if (enableUpdaterTracking) {
1350+
// If we have pending work still, restore the original updaters
1351+
restorePendingUpdaters(finishedRoot, committedExpirationTime);
1352+
}
13241353
retryCache.add(thenable);
13251354
thenable.then(retry, retry);
13261355
}

packages/react-reconciler/src/ReactFiberRoot.js

Lines changed: 20 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,10 @@ import type {Interaction} from 'scheduler/src/Tracing';
1717
import {noTimeout} from './ReactFiberHostConfig';
1818
import {createHostRootFiber} from './ReactFiber';
1919
import {NoWork} from './ReactFiberExpirationTime';
20-
import {enableSchedulerTracing} from 'shared/ReactFeatureFlags';
20+
import {
21+
enableSchedulerTracing,
22+
enableUpdaterTracking,
23+
} from 'shared/ReactFeatureFlags';
2124
import {unstable_getThreadID} from 'scheduler/tracing';
2225

2326
// TODO: This should be lifted into the renderer.
@@ -30,6 +33,9 @@ export type Batch = {
3033

3134
export type PendingInteractionMap = Map<ExpirationTime, Set<Interaction>>;
3235

36+
// Map of expiration time to all pending "updaters" which in turn is a map of Fibers to reference counts.
37+
export type PendingUpdatersMap = Map<ExpirationTime, Set<Fiber>>;
38+
3339
type BaseFiberRootProperties = {|
3440
// The type of root (legacy, batched, concurrent, etc.)
3541
tag: RootTag,
@@ -83,6 +89,13 @@ type ProfilingOnlyFiberRootProperties = {|
8389
pendingInteractionMap: PendingInteractionMap,
8490
|};
8591

92+
// The following attributes are only used by DevTools and are only present in DEV builds.
93+
// They enable DevTools Profiler UI to show which Fiber(s) scheduled a given commit.
94+
type UpdaterTrackingOnlyFiberRootProperties = {|
95+
memoizedUpdaters: Set<Fiber>,
96+
pendingUpdatersMap: PendingUpdatersMap,
97+
|};
98+
8699
// Exported FiberRoot type includes all properties,
87100
// To avoid requiring potentially error-prone :any casts throughout the project.
88101
// Profiling properties are only safe to access in profiling builds (when enableSchedulerTracing is true).
@@ -91,6 +104,7 @@ type ProfilingOnlyFiberRootProperties = {|
91104
export type FiberRoot = {
92105
...BaseFiberRootProperties,
93106
...ProfilingOnlyFiberRootProperties,
107+
...UpdaterTrackingOnlyFiberRootProperties,
94108
};
95109

96110
function FiberRootNode(containerInfo, tag, hydrate) {
@@ -117,6 +131,11 @@ function FiberRootNode(containerInfo, tag, hydrate) {
117131
this.memoizedInteractions = new Set();
118132
this.pendingInteractionMap = new Map();
119133
}
134+
135+
if (enableUpdaterTracking) {
136+
this.memoizedUpdaters = new Set();
137+
this.pendingUpdatersMap = new Map();
138+
}
120139
}
121140

122141
export function createFiberRoot(

packages/react-reconciler/src/ReactFiberScheduler.js

Lines changed: 102 additions & 37 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,7 @@ import type {Interaction} from 'scheduler/src/Tracing';
1919
import {
2020
warnAboutDeprecatedLifecycles,
2121
enableUserTimingAPI,
22+
enableUpdaterTracking,
2223
enableSuspenseServerRenderer,
2324
replayFailedUnitOfWorkWithInvokeGuardedCallback,
2425
enableProfilerTimer,
@@ -337,6 +338,16 @@ export function scheduleUpdateOnFiber(
337338
return;
338339
}
339340

341+
if (enableUpdaterTracking) {
342+
const pendingUpdatersMap = root.pendingUpdatersMap;
343+
let updaters = pendingUpdatersMap.get(expirationTime);
344+
if (updaters == null) {
345+
updaters = new Set();
346+
pendingUpdatersMap.set(expirationTime, updaters);
347+
}
348+
updaters.add(fiber);
349+
}
350+
340351
root.pingTime = NoWork;
341352

342353
checkForInterruption(fiber, expirationTime);
@@ -1292,6 +1303,12 @@ function commitRootImpl(root) {
12921303
// This usually means we've finished all the work, but it can also happen
12931304
// when something gets downprioritized during render, like a hidden tree.
12941305
root.lastPendingTime = firstPendingTimeBeforeCommit;
1306+
1307+
if (enableSchedulerTracing) {
1308+
if (firstPendingTimeBeforeCommit !== NoWork) {
1309+
restorePendingUpdaters(root, root.lastPendingTime);
1310+
}
1311+
}
12951312
}
12961313

12971314
if (root === workInProgressRoot) {
@@ -1377,7 +1394,13 @@ function commitRootImpl(root) {
13771394
nextEffect = firstEffect;
13781395
do {
13791396
if (__DEV__) {
1380-
invokeGuardedCallback(null, commitMutationEffects, null);
1397+
invokeGuardedCallback(
1398+
null,
1399+
commitMutationEffects,
1400+
null,
1401+
root,
1402+
expirationTime,
1403+
);
13811404
if (hasCaughtError()) {
13821405
invariant(nextEffect !== null, 'Should be working on an effect.');
13831406
const error = clearCaughtError();
@@ -1386,7 +1409,7 @@ function commitRootImpl(root) {
13861409
}
13871410
} else {
13881411
try {
1389-
commitMutationEffects();
1412+
commitMutationEffects(root, expirationTime);
13901413
} catch (error) {
13911414
invariant(nextEffect !== null, 'Should be working on an effect.');
13921415
captureCommitPhaseError(nextEffect, error);
@@ -1540,7 +1563,10 @@ function commitBeforeMutationEffects() {
15401563
}
15411564
}
15421565

1543-
function commitMutationEffects() {
1566+
function commitMutationEffects(
1567+
root: FiberRoot,
1568+
committedExpirationTime: ExpirationTime,
1569+
) {
15441570
// TODO: Should probably move the bulk of this function to commitWork.
15451571
while (nextEffect !== null) {
15461572
setCurrentDebugFiberInDEV(nextEffect);
@@ -1582,12 +1608,12 @@ function commitMutationEffects() {
15821608

15831609
// Update
15841610
const current = nextEffect.alternate;
1585-
commitWork(current, nextEffect);
1611+
commitWork(root, current, nextEffect, committedExpirationTime);
15861612
break;
15871613
}
15881614
case Update: {
15891615
const current = nextEffect.alternate;
1590-
commitWork(current, nextEffect);
1616+
commitWork(root, current, nextEffect, committedExpirationTime);
15911617
break;
15921618
}
15931619
case Deletion: {
@@ -1843,7 +1869,12 @@ export function retryTimedOutBoundary(boundaryFiber: Fiber) {
18431869
}
18441870
}
18451871

1846-
export function resolveRetryThenable(boundaryFiber: Fiber, thenable: Thenable) {
1872+
export function resolveRetryThenable(
1873+
boundaryFiber: Fiber,
1874+
thenable: Thenable,
1875+
root: FiberRoot,
1876+
committedExpirationTime: ExpirationTime,
1877+
) {
18471878
let retryCache: WeakSet<Thenable> | Set<Thenable> | null;
18481879
if (enableSuspenseServerRenderer) {
18491880
switch (boundaryFiber.tag) {
@@ -2161,6 +2192,24 @@ function warnIfNotCurrentlyActingUpdatesInDEV(fiber: Fiber): void {
21612192
}
21622193
}
21632194

2195+
export function restorePendingUpdaters(
2196+
root: FiberRoot,
2197+
expirationTime: ExpirationTime,
2198+
): void {
2199+
if (!enableUpdaterTracking) {
2200+
return;
2201+
}
2202+
const pendingUpdatersMap = root.pendingUpdatersMap;
2203+
let updaters = pendingUpdatersMap.get(expirationTime);
2204+
if (updaters == null) {
2205+
updaters = new Set();
2206+
pendingUpdatersMap.set(expirationTime, updaters);
2207+
}
2208+
root.memoizedUpdaters.forEach(schedulingFiber => {
2209+
((updaters: any): Set<Fiber>).add(schedulingFiber);
2210+
});
2211+
}
2212+
21642213
export const warnIfNotCurrentlyActingUpdatesInDev = warnIfNotCurrentlyActingUpdatesInDEV;
21652214

21662215
let componentsWithSuspendedDiscreteUpdates = null;
@@ -2277,42 +2326,58 @@ function schedulePendingInteraction(root, expirationTime) {
22772326

22782327
function startWorkOnPendingInteraction(root, expirationTime) {
22792328
// This is called when new work is started on a root.
2280-
if (!enableSchedulerTracing) {
2281-
return;
2282-
}
22832329

2284-
// Determine which interactions this batch of work currently includes, So that
2285-
// we can accurately attribute time spent working on it, And so that cascading
2286-
// work triggered during the render phase will be associated with it.
2287-
const interactions: Set<Interaction> = new Set();
2288-
root.pendingInteractionMap.forEach(
2289-
(scheduledInteractions, scheduledExpirationTime) => {
2330+
if (enableUpdaterTracking) {
2331+
const memoizedUpdaters: Set<Fiber> = new Set();
2332+
const pendingUpdatersMap = root.pendingUpdatersMap;
2333+
pendingUpdatersMap.forEach((updaters, scheduledExpirationTime) => {
22902334
if (scheduledExpirationTime >= expirationTime) {
2291-
scheduledInteractions.forEach(interaction =>
2292-
interactions.add(interaction),
2293-
);
2335+
pendingUpdatersMap.delete(scheduledExpirationTime);
2336+
updaters.forEach(fiber => memoizedUpdaters.add(fiber));
22942337
}
2295-
},
2296-
);
2338+
});
22972339

2298-
// Store the current set of interactions on the FiberRoot for a few reasons:
2299-
// We can re-use it in hot functions like renderRoot() without having to
2300-
// recalculate it. We will also use it in commitWork() to pass to any Profiler
2301-
// onRender() hooks. This also provides DevTools with a way to access it when
2302-
// the onCommitRoot() hook is called.
2303-
root.memoizedInteractions = interactions;
2340+
// Store the current set of interactions on the FiberRoot for a few reasons:
2341+
// We can re-use it in hot functions like renderRoot() without having to
2342+
// recalculate it. This also provides DevTools with a way to access it when
2343+
// the onCommitRoot() hook is called.
2344+
root.memoizedUpdaters = memoizedUpdaters;
2345+
}
23042346

2305-
if (interactions.size > 0) {
2306-
const subscriber = __subscriberRef.current;
2307-
if (subscriber !== null) {
2308-
const threadID = computeThreadID(root, expirationTime);
2309-
try {
2310-
subscriber.onWorkStarted(interactions, threadID);
2311-
} catch (error) {
2312-
// If the subscriber throws, rethrow it in a separate task
2313-
scheduleCallback(ImmediatePriority, () => {
2314-
throw error;
2315-
});
2347+
if (enableSchedulerTracing) {
2348+
// Determine which interactions this batch of work currently includes, So that
2349+
// we can accurately attribute time spent working on it, And so that cascading
2350+
// work triggered during the render phase will be associated with it.
2351+
const interactions: Set<Interaction> = new Set();
2352+
root.pendingInteractionMap.forEach(
2353+
(scheduledInteractions, scheduledExpirationTime) => {
2354+
if (scheduledExpirationTime >= expirationTime) {
2355+
scheduledInteractions.forEach(interaction =>
2356+
interactions.add(interaction),
2357+
);
2358+
}
2359+
},
2360+
);
2361+
2362+
// Store the current set of interactions on the FiberRoot for a few reasons:
2363+
// We can re-use it in hot functions like renderRoot() without having to
2364+
// recalculate it. We will also use it in commitWork() to pass to any Profiler
2365+
// onRender() hooks. This also provides DevTools with a way to access it when
2366+
// the onCommitRoot() hook is called.
2367+
root.memoizedInteractions = interactions;
2368+
2369+
if (interactions.size > 0) {
2370+
const subscriber = __subscriberRef.current;
2371+
if (subscriber !== null) {
2372+
const threadID = computeThreadID(root, expirationTime);
2373+
try {
2374+
subscriber.onWorkStarted(interactions, threadID);
2375+
} catch (error) {
2376+
// If the subscriber throws, rethrow it in a separate task
2377+
scheduleCallback(ImmediatePriority, () => {
2378+
throw error;
2379+
});
2380+
}
23162381
}
23172382
}
23182383
}

0 commit comments

Comments
 (0)