@@ -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.
263264let 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+
265271let stashedWorkInProgressProperties ;
266272let replayUnitOfWork ;
267273let 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-
17261708function 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+
25142531export {
25152532 requestCurrentTime ,
25162533 computeExpirationForFiber,
@@ -2533,4 +2550,5 @@ export {
25332550 interactiveUpdates,
25342551 flushInteractiveUpdates,
25352552 computeUniqueAsyncExpiration,
2553+ markSuspenseDidTimeout,
25362554} ;
0 commit comments