Skip to content

Commit f0562c7

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

12 files changed

+653
-41
lines changed

packages/react-reconciler/src/ReactFiberCommitWork.js

Lines changed: 27 additions & 4 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;
@@ -1321,6 +1340,10 @@ function commitSuspenseComponent(finishedWork: Fiber) {
13211340
if (enableSchedulerTracing) {
13221341
retry = Schedule_tracing_wrap(retry);
13231342
}
1343+
if (enableUpdaterTracking) {
1344+
// If we have pending work still, restore the original updaters
1345+
restorePendingUpdaters(finishedRoot, committedExpirationTime);
1346+
}
13241347
retryCache.add(thenable);
13251348
thenable.then(retry, retry);
13261349
}

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: 96 additions & 36 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 (enableUpdaterTracking) {
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: {
@@ -2161,6 +2187,24 @@ function warnIfNotCurrentlyActingUpdatesInDEV(fiber: Fiber): void {
21612187
}
21622188
}
21632189

2190+
export function restorePendingUpdaters(
2191+
root: FiberRoot,
2192+
expirationTime: ExpirationTime,
2193+
): void {
2194+
if (!enableUpdaterTracking) {
2195+
return;
2196+
}
2197+
const pendingUpdatersMap = root.pendingUpdatersMap;
2198+
let updaters = pendingUpdatersMap.get(expirationTime);
2199+
if (updaters == null) {
2200+
updaters = new Set();
2201+
pendingUpdatersMap.set(expirationTime, updaters);
2202+
}
2203+
root.memoizedUpdaters.forEach(schedulingFiber => {
2204+
((updaters: any): Set<Fiber>).add(schedulingFiber);
2205+
});
2206+
}
2207+
21642208
export const warnIfNotCurrentlyActingUpdatesInDev = warnIfNotCurrentlyActingUpdatesInDEV;
21652209

21662210
let componentsWithSuspendedDiscreteUpdates = null;
@@ -2277,42 +2321,58 @@ function schedulePendingInteraction(root, expirationTime) {
22772321

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

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) => {
2325+
if (enableUpdaterTracking) {
2326+
const memoizedUpdaters: Set<Fiber> = new Set();
2327+
const pendingUpdatersMap = root.pendingUpdatersMap;
2328+
pendingUpdatersMap.forEach((updaters, scheduledExpirationTime) => {
22902329
if (scheduledExpirationTime >= expirationTime) {
2291-
scheduledInteractions.forEach(interaction =>
2292-
interactions.add(interaction),
2293-
);
2330+
pendingUpdatersMap.delete(scheduledExpirationTime);
2331+
updaters.forEach(fiber => memoizedUpdaters.add(fiber));
22942332
}
2295-
},
2296-
);
2333+
});
22972334

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;
2335+
// Store the current set of interactions on the FiberRoot for a few reasons:
2336+
// We can re-use it in hot functions like renderRoot() without having to
2337+
// recalculate it. This also provides DevTools with a way to access it when
2338+
// the onCommitRoot() hook is called.
2339+
root.memoizedUpdaters = memoizedUpdaters;
2340+
}
23042341

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

packages/react-reconciler/src/ReactFiberUnwindWork.js

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -40,6 +40,7 @@ import {
4040
import {
4141
enableSchedulerTracing,
4242
enableSuspenseServerRenderer,
43+
enableUpdaterTracking,
4344
enableEventAPI,
4445
} from 'shared/ReactFeatureFlags';
4546
import {NoMode, BatchedMode} from './ReactTypeOfMode';
@@ -76,6 +77,7 @@ import {
7677
pingSuspendedRoot,
7778
resolveRetryThenable,
7879
checkForWrongSuspensePriorityInDEV,
80+
restorePendingUpdaters,
7981
} from './ReactFiberScheduler';
8082

8183
import invariant from 'shared/invariant';
@@ -187,6 +189,10 @@ function attachPingListener(
187189
if (enableSchedulerTracing) {
188190
ping = Schedule_tracing_wrap(ping);
189191
}
192+
if (enableUpdaterTracking) {
193+
// If we have pending work still, restore the original updaters
194+
restorePendingUpdaters(root, renderExpirationTime);
195+
}
190196
thenable.then(ping, ping);
191197
}
192198
}
@@ -203,6 +209,11 @@ function throwException(
203209
// Its effect list is no longer valid.
204210
sourceFiber.firstEffect = sourceFiber.lastEffect = null;
205211

212+
if (enableUpdaterTracking) {
213+
// If we have pending work still, restore the original updaters
214+
restorePendingUpdaters(root, renderExpirationTime);
215+
}
216+
206217
if (
207218
value !== null &&
208219
typeof value === 'object' &&

0 commit comments

Comments
 (0)