Skip to content

Commit e5dd64b

Browse files
author
Brian Vaughn
committed
Added several new interaction tracing tests and edge case fixes
1 parent 8bc0bca commit e5dd64b

3 files changed

Lines changed: 420 additions & 67 deletions

File tree

packages/react-reconciler/src/ReactFiberCommitWork.js

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -76,6 +76,7 @@ import {
7676
} from './ReactFiberHostConfig';
7777
import {
7878
captureCommitPhaseError,
79+
markSuspenseDidTimeout,
7980
requestCurrentTime,
8081
scheduleWork,
8182
} from './ReactFiberScheduler';
@@ -361,6 +362,12 @@ function commitLifeCycles(
361362
// entire queue. Any non-null value works.
362363
// $FlowFixMe - Intentionally using a value other than an UpdateQueue.
363364
finishedWork.updateQueue = emptyObject;
365+
if (enableSchedulerTracing) {
366+
// Handles the special case of a suspended sync render.
367+
// In this case we don't decrement the interaction count on Placeholder commit.
368+
// Instead we wait until the suspended promise has resolved.
369+
markSuspenseDidTimeout();
370+
}
364371
scheduleWork(finishedWork, Sync);
365372
} else {
366373
// In strict mode, the Update effect is used to record the time at

packages/react-reconciler/src/ReactFiberScheduler.js

Lines changed: 85 additions & 67 deletions
Original file line numberDiff line numberDiff line change
@@ -260,8 +260,14 @@ let interruptedBy: Fiber | null = null;
260260

261261
// Do not decrement interaction counts in the event of suspense timeouts.
262262
// This would lead to prematurely calling the interaction-complete hook.
263+
// Instead we wait until the suspended promise has resolved.
263264
let suspenseDidTimeout: boolean = false;
264265

266+
// Do not remove the current interactions from the priority map on commit.
267+
// This covers a special case of sync rendering with suspense.
268+
// In this case we leave interactions in the Map until the suspended promise resolves.
269+
let preservePendingInteractionsOnCommit: boolean = true;
270+
265271
let stashedWorkInProgressProperties;
266272
let replayUnitOfWork;
267273
let isReplayingFailedUnitOfWork;
@@ -583,11 +589,17 @@ function commitRoot(root: FiberRoot, finishedWork: Fiber): void {
583589
// These are stored as an Array rather than a Set,
584590
// Because the same interaction may be pending for multiple expiration times,
585591
// In which case it's important that we decrement the count the right number of times after finishing.
586-
root.pendingInteractionMap.forEach(
592+
const pendingInteractionMap = root.pendingInteractionMap;
593+
pendingInteractionMap.forEach(
587594
(scheduledInteractions, scheduledExpirationTime) => {
588595
if (scheduledExpirationTime <= committedExpirationTime) {
589596
committedInteractions.push(...Array.from(scheduledInteractions));
590-
root.pendingInteractionMap.delete(scheduledExpirationTime);
597+
598+
// Don't delete interactions from the map if we're frozen due to suspense.
599+
// In this case, the interactions should be moved to the suspense retry time.
600+
if (!suspenseDidTimeout && !preservePendingInteractionsOnCommit) {
601+
pendingInteractionMap.delete(scheduledExpirationTime);
602+
}
591603
}
592604
},
593605
);
@@ -1174,14 +1186,6 @@ function renderRoot(
11741186

11751187
const expirationTime = root.nextExpirationTimeToWorkOn;
11761188

1177-
let prevInteractions: Set<Interaction> = (null: any);
1178-
if (enableSchedulerTracing) {
1179-
// We're about to start new traced work.
1180-
// Restore pending interactions so cascading work triggered during the render phase will be accounted for.
1181-
prevInteractions = __interactionsRef.current;
1182-
__interactionsRef.current = root.memoizedInteractions;
1183-
}
1184-
11851189
// Check if we're starting from a fresh stack, or if we're resuming from
11861190
// previously yielded work.
11871191
if (
@@ -1201,6 +1205,9 @@ function renderRoot(
12011205
root.pendingCommitExpirationTime = NoWork;
12021206

12031207
if (enableSchedulerTracing) {
1208+
preservePendingInteractionsOnCommit = suspenseDidTimeout;
1209+
suspenseDidTimeout = false;
1210+
12041211
// Determine which interactions this batch of work currently includes,
12051212
// So that we can accurately attribute time spent working on it,
12061213
// And so that cascading work triggered during the render phase will be associated with it.
@@ -1244,6 +1251,14 @@ function renderRoot(
12441251
}
12451252
}
12461253

1254+
let prevInteractions: Set<Interaction> = (null: any);
1255+
if (enableSchedulerTracing) {
1256+
// We're about to start new traced work.
1257+
// Restore pending interactions so cascading work triggered during the render phase will be accounted for.
1258+
prevInteractions = __interactionsRef.current;
1259+
__interactionsRef.current = root.memoizedInteractions;
1260+
}
1261+
12471262
let didFatal = false;
12481263

12491264
startWorkLoopTimer(nextUnitOfWork);
@@ -1403,6 +1418,7 @@ function renderRoot(
14031418

14041419
if (enableSuspense && !isExpired && nextLatestAbsoluteTimeoutMs !== -1) {
14051420
// The tree was suspended.
1421+
suspenseDidTimeout = true;
14061422
const suspendedExpirationTime = expirationTime;
14071423
markSuspendedPriorityLevel(root, suspendedExpirationTime);
14081424

@@ -1603,29 +1619,38 @@ function retrySuspendedRoot(
16031619
if (isPriorityLevelSuspended(root, suspendedTime)) {
16041620
// Ping at the original level
16051621
retryTime = suspendedTime;
1622+
16061623
markPingedPriorityLevel(root, retryTime);
16071624
} else {
16081625
// Placeholder already timed out. Compute a new expiration time
16091626
const currentTime = requestCurrentTime();
16101627
retryTime = computeExpirationForFiber(currentTime, fiber);
16111628
markPendingPriorityLevel(root, retryTime);
1629+
1630+
if (enableSchedulerTracing) {
1631+
// Interaction counts should not get decremented until suspended work resolves.
1632+
// Update their position in the map to account for this expiration time change.
1633+
// This ensures they aren't decremented prematurely (e.g. by high pri interleaved work).
1634+
const pendingInteractionMap = root.pendingInteractionMap;
1635+
const suspendedInteractions = pendingInteractionMap.get(suspendedTime);
1636+
1637+
if (suspendedInteractions != null) {
1638+
const retryInteractions = pendingInteractionMap.get(retryTime);
1639+
if (retryInteractions != null) {
1640+
suspendedInteractions.forEach(interaction => {
1641+
retryInteractions.add(interaction);
1642+
});
1643+
} else {
1644+
pendingInteractionMap.set(retryTime, suspendedInteractions);
1645+
}
1646+
}
1647+
}
16121648
}
16131649

16141650
scheduleWorkToRoot(fiber, retryTime);
16151651
const rootExpirationTime = root.expirationTime;
16161652
if (rootExpirationTime !== NoWork) {
1617-
if (enableSchedulerTracing) {
1618-
// Restore previous interactions so that new work is associated with them.
1619-
let prevInteractions = __interactionsRef.current;
1620-
__interactionsRef.current = root.memoizedInteractions;
1621-
// Because suspense timeouts do not decrement the interaction count,
1622-
// Continued suspense work should also not increment the count.
1623-
storeInteractionsForExpirationTime(root, rootExpirationTime, false);
1624-
requestWork(root, rootExpirationTime);
1625-
__interactionsRef.current = prevInteractions;
1626-
} else {
1627-
requestWork(root, rootExpirationTime);
1628-
}
1653+
requestWork(root, rootExpirationTime);
16291654
}
16301655
}
16311656
}
@@ -1680,49 +1705,6 @@ function scheduleWorkToRoot(fiber: Fiber, expirationTime): FiberRoot | null {
16801705
return null;
16811706
}
16821707

1683-
function storeInteractionsForExpirationTime(
1684-
root: FiberRoot,
1685-
expirationTime: ExpirationTime,
1686-
updateInteractionCounts: boolean,
1687-
): void {
1688-
if (!enableSchedulerTracing) {
1689-
return;
1690-
}
1691-
1692-
const interactions = __interactionsRef.current;
1693-
if (interactions.size > 0) {
1694-
const pendingInteractions = root.pendingInteractionMap.get(expirationTime);
1695-
if (pendingInteractions != null) {
1696-
interactions.forEach(interaction => {
1697-
if (updateInteractionCounts && !pendingInteractions.has(interaction)) {
1698-
// Update the pending async work count for previously unscheduled interaction.
1699-
interaction.__count++;
1700-
}
1701-
1702-
pendingInteractions.add(interaction);
1703-
});
1704-
} else {
1705-
root.pendingInteractionMap.set(expirationTime, new Set(interactions));
1706-
1707-
// Update the pending async work count for the current interactions.
1708-
if (updateInteractionCounts) {
1709-
interactions.forEach(interaction => {
1710-
interaction.__count++;
1711-
});
1712-
}
1713-
}
1714-
1715-
const subscriber = __subscriberRef.current;
1716-
if (subscriber !== null) {
1717-
const threadID = computeThreadID(
1718-
expirationTime,
1719-
root.interactionThreadID,
1720-
);
1721-
subscriber.onWorkScheduled(interactions, threadID);
1722-
}
1723-
}
1724-
}
1725-
17261708
function scheduleWork(fiber: Fiber, expirationTime: ExpirationTime) {
17271709
recordScheduleUpdate();
17281710

@@ -1745,7 +1727,37 @@ function scheduleWork(fiber: Fiber, expirationTime: ExpirationTime) {
17451727
}
17461728

17471729
if (enableSchedulerTracing) {
1748-
storeInteractionsForExpirationTime(root, expirationTime, true);
1730+
const interactions = __interactionsRef.current;
1731+
if (interactions.size > 0) {
1732+
const pendingInteractionMap = root.pendingInteractionMap;
1733+
const pendingInteractions = pendingInteractionMap.get(expirationTime);
1734+
if (pendingInteractions != null) {
1735+
interactions.forEach(interaction => {
1736+
if (!pendingInteractions.has(interaction)) {
1737+
// Update the pending async work count for previously unscheduled interaction.
1738+
interaction.__count++;
1739+
}
1740+
1741+
pendingInteractions.add(interaction);
1742+
});
1743+
} else {
1744+
pendingInteractionMap.set(expirationTime, new Set(interactions));
1745+
1746+
// Update the pending async work count for the current interactions.
1747+
interactions.forEach(interaction => {
1748+
interaction.__count++;
1749+
});
1750+
}
1751+
1752+
const subscriber = __subscriberRef.current;
1753+
if (subscriber !== null) {
1754+
const threadID = computeThreadID(
1755+
expirationTime,
1756+
root.interactionThreadID,
1757+
);
1758+
subscriber.onWorkScheduled(interactions, threadID);
1759+
}
1760+
}
17491761
}
17501762

17511763
if (
@@ -1940,7 +1952,6 @@ function onTimeout(root, finishedWork, suspendedExpirationTime) {
19401952
// Because we know we still need to do more work in this case.
19411953
suspenseDidTimeout = true;
19421954
flushRoot(root, suspendedExpirationTime);
1943-
suspenseDidTimeout = false;
19441955
} else {
19451956
flushRoot(root, suspendedExpirationTime);
19461957
}
@@ -2511,6 +2522,12 @@ function flushControlled(fn: () => mixed): void {
25112522
}
25122523
}
25132524

2525+
function markSuspenseDidTimeout() {
2526+
if (enableSchedulerTracing) {
2527+
suspenseDidTimeout = true;
2528+
}
2529+
}
2530+
25142531
export {
25152532
requestCurrentTime,
25162533
computeExpirationForFiber,
@@ -2533,4 +2550,5 @@ export {
25332550
interactiveUpdates,
25342551
flushInteractiveUpdates,
25352552
computeUniqueAsyncExpiration,
2553+
markSuspenseDidTimeout,
25362554
};

0 commit comments

Comments
 (0)