@@ -260,7 +260,8 @@ 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- let suspenseDidTimeout : boolean = false ;
263+ // Instead we wait until the suspended promise has resolved.
264+ let interactionsHaveBeenSuspended : boolean = false ;
264265
265266let stashedWorkInProgressProperties ;
266267let replayUnitOfWork ;
@@ -566,31 +567,11 @@ function commitRoot(root: FiberRoot, finishedWork: Fiber): void {
566567 markCommittedPriorityLevels ( root , earliestRemainingTimeBeforeCommit ) ;
567568
568569 let prevInteractions : Set < Interaction > = ( null : any ) ;
569- let committedInteractions : Array < Interaction > = enableSchedulerTracing
570- ? [ ]
571- : ( null : any ) ;
572570 if ( enableSchedulerTracing ) {
573571 // Restore any pending interactions at this point,
574572 // So that cascading work triggered during the render phase will be accounted for.
575573 prevInteractions = __interactionsRef . current ;
576574 __interactionsRef . current = root . memoizedInteractions ;
577-
578- // We are potentially finished with the current batch of interactions.
579- // So we should clear them out of the pending interaction map.
580- // We do this at the start of commit in case cascading work is scheduled by commit phase lifecycles.
581- // In that event, interaction data may be added back into the pending map for a future commit.
582- // We also store the interactions we are about to commit so that we can notify subscribers after we're done.
583- // These are stored as an Array rather than a Set,
584- // Because the same interaction may be pending for multiple expiration times,
585- // In which case it's important that we decrement the count the right number of times after finishing.
586- root . pendingInteractionMap . forEach (
587- ( scheduledInteractions , scheduledExpirationTime ) => {
588- if ( scheduledExpirationTime <= committedExpirationTime ) {
589- committedInteractions . push ( ...Array . from ( scheduledInteractions ) ) ;
590- root . pendingInteractionMap . delete ( scheduledExpirationTime ) ;
591- }
592- } ,
593- ) ;
594575 }
595576
596577 // Reset this to null before calling lifecycles
@@ -789,27 +770,38 @@ function commitRoot(root: FiberRoot, finishedWork: Fiber): void {
789770 unhandledError = error ;
790771 }
791772 } finally {
792- // Don't update interaction counts if we're frozen due to suspense.
793- // In this case, we can skip the completed-work check entirely.
794- if ( ! suspenseDidTimeout ) {
795- // Now that we're done, check the completed batch of interactions.
796- // If no more work is outstanding for a given interaction,
797- // We need to notify the subscribers that it's finished.
798- committedInteractions . forEach ( interaction => {
799- interaction . __count -- ;
800- if ( subscriber !== null && interaction . __count === 0 ) {
801- try {
802- subscriber . onInteractionScheduledWorkCompleted ( interaction ) ;
803- } catch ( error ) {
804- // It's not safe for commitRoot() to throw.
805- // Store the error for now and we'll re-throw in finishRendering().
806- if ( ! hasUnhandledError ) {
807- hasUnhandledError = true ;
808- unhandledError = error ;
809- }
773+ if ( ! interactionsHaveBeenSuspended ) {
774+ // Clear completed interactions from the pending Map.
775+ // Unless the render was suspended or cascading work was scheduled,
776+ // In which case– leave pending interactions until the subsequent render.
777+ const pendingInteractionMap = root . pendingInteractionMap ;
778+ pendingInteractionMap . forEach (
779+ ( scheduledInteractions , scheduledExpirationTime ) => {
780+ if (
781+ earliestRemainingTimeAfterCommit === NoWork ||
782+ scheduledExpirationTime < earliestRemainingTimeAfterCommit
783+ ) {
784+ pendingInteractionMap . delete ( scheduledExpirationTime ) ;
785+
786+ scheduledInteractions . forEach ( interaction => {
787+ interaction . __count -- ;
788+
789+ if ( subscriber !== null && interaction . __count === 0 ) {
790+ try {
791+ subscriber . onInteractionScheduledWorkCompleted ( interaction ) ;
792+ } catch ( error ) {
793+ // It's not safe for commitRoot() to throw.
794+ // Store the error for now and we'll re-throw in finishRendering().
795+ if ( ! hasUnhandledError ) {
796+ hasUnhandledError = true ;
797+ unhandledError = error ;
798+ }
799+ }
800+ }
801+ } ) ;
810802 }
811- }
812- } ) ;
803+ } ,
804+ ) ;
813805 }
814806 }
815807 }
@@ -1174,14 +1166,6 @@ function renderRoot(
11741166
11751167 const expirationTime = root . nextExpirationTimeToWorkOn ;
11761168
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-
11851169 // Check if we're starting from a fresh stack, or if we're resuming from
11861170 // previously yielded work.
11871171 if (
@@ -1201,6 +1185,11 @@ function renderRoot(
12011185 root . pendingCommitExpirationTime = NoWork ;
12021186
12031187 if ( enableSchedulerTracing ) {
1188+ // Reset this flag once we start rendering a new root or at a new priority.
1189+ // This might indicate that suspended work has completed.
1190+ // If not, the flag will be reset.
1191+ interactionsHaveBeenSuspended = false ;
1192+
12041193 // Determine which interactions this batch of work currently includes,
12051194 // So that we can accurately attribute time spent working on it,
12061195 // And so that cascading work triggered during the render phase will be associated with it.
@@ -1244,6 +1233,14 @@ function renderRoot(
12441233 }
12451234 }
12461235
1236+ let prevInteractions : Set < Interaction > = ( null : any ) ;
1237+ if ( enableSchedulerTracing ) {
1238+ // We're about to start new traced work.
1239+ // Restore pending interactions so cascading work triggered during the render phase will be accounted for.
1240+ prevInteractions = __interactionsRef . current ;
1241+ __interactionsRef . current = root . memoizedInteractions ;
1242+ }
1243+
12471244 let didFatal = false ;
12481245
12491246 startWorkLoopTimer ( nextUnitOfWork ) ;
@@ -1403,6 +1400,9 @@ function renderRoot(
14031400
14041401 if ( enableSuspense && ! isExpired && nextLatestAbsoluteTimeoutMs !== - 1 ) {
14051402 // The tree was suspended.
1403+ if ( enableSchedulerTracing ) {
1404+ interactionsHaveBeenSuspended = true ;
1405+ }
14061406 const suspendedExpirationTime = expirationTime ;
14071407 markSuspendedPriorityLevel ( root , suspendedExpirationTime ) ;
14081408
@@ -1600,6 +1600,7 @@ function retrySuspendedRoot(
16001600 if ( isPriorityLevelSuspended ( root , suspendedTime ) ) {
16011601 // Ping at the original level
16021602 retryTime = suspendedTime ;
1603+
16031604 markPingedPriorityLevel ( root , retryTime ) ;
16041605 } else {
16051606 // Placeholder already timed out. Compute a new expiration time
@@ -1611,18 +1612,7 @@ function retrySuspendedRoot(
16111612 scheduleWorkToRoot ( fiber , retryTime ) ;
16121613 const rootExpirationTime = root . expirationTime ;
16131614 if ( rootExpirationTime !== NoWork ) {
1614- if ( enableSchedulerTracing ) {
1615- // Restore previous interactions so that new work is associated with them.
1616- let prevInteractions = __interactionsRef . current ;
1617- __interactionsRef . current = root . memoizedInteractions ;
1618- // Because suspense timeouts do not decrement the interaction count,
1619- // Continued suspense work should also not increment the count.
1620- storeInteractionsForExpirationTime ( root , rootExpirationTime , false ) ;
1621- requestWork ( root , rootExpirationTime ) ;
1622- __interactionsRef . current = prevInteractions ;
1623- } else {
1624- requestWork ( root , rootExpirationTime ) ;
1625- }
1615+ requestWork ( root , rootExpirationTime ) ;
16261616 }
16271617 }
16281618}
@@ -1677,49 +1667,6 @@ function scheduleWorkToRoot(fiber: Fiber, expirationTime): FiberRoot | null {
16771667 return null ;
16781668}
16791669
1680- function storeInteractionsForExpirationTime (
1681- root : FiberRoot ,
1682- expirationTime : ExpirationTime ,
1683- updateInteractionCounts : boolean ,
1684- ) : void {
1685- if ( ! enableSchedulerTracing ) {
1686- return ;
1687- }
1688-
1689- const interactions = __interactionsRef . current ;
1690- if ( interactions . size > 0 ) {
1691- const pendingInteractions = root . pendingInteractionMap . get ( expirationTime ) ;
1692- if ( pendingInteractions != null ) {
1693- interactions . forEach ( interaction => {
1694- if ( updateInteractionCounts && ! pendingInteractions . has ( interaction ) ) {
1695- // Update the pending async work count for previously unscheduled interaction.
1696- interaction . __count ++ ;
1697- }
1698-
1699- pendingInteractions . add ( interaction ) ;
1700- } ) ;
1701- } else {
1702- root . pendingInteractionMap . set ( expirationTime , new Set ( interactions ) ) ;
1703-
1704- // Update the pending async work count for the current interactions.
1705- if ( updateInteractionCounts ) {
1706- interactions . forEach ( interaction => {
1707- interaction . __count ++ ;
1708- } ) ;
1709- }
1710- }
1711-
1712- const subscriber = __subscriberRef . current ;
1713- if ( subscriber !== null ) {
1714- const threadID = computeThreadID (
1715- expirationTime ,
1716- root . interactionThreadID ,
1717- ) ;
1718- subscriber . onWorkScheduled ( interactions , threadID ) ;
1719- }
1720- }
1721- }
1722-
17231670function scheduleWork ( fiber : Fiber , expirationTime : ExpirationTime ) {
17241671 recordScheduleUpdate ( ) ;
17251672
@@ -1742,7 +1689,37 @@ function scheduleWork(fiber: Fiber, expirationTime: ExpirationTime) {
17421689 }
17431690
17441691 if ( enableSchedulerTracing ) {
1745- storeInteractionsForExpirationTime ( root , expirationTime , true ) ;
1692+ const interactions = __interactionsRef . current ;
1693+ if ( interactions . size > 0 ) {
1694+ const pendingInteractionMap = root . pendingInteractionMap ;
1695+ const pendingInteractions = pendingInteractionMap . get ( expirationTime ) ;
1696+ if ( pendingInteractions != null ) {
1697+ interactions . forEach ( interaction => {
1698+ if ( ! pendingInteractions . has ( interaction ) ) {
1699+ // Update the pending async work count for previously unscheduled interaction.
1700+ interaction . __count ++ ;
1701+ }
1702+
1703+ pendingInteractions . add ( interaction ) ;
1704+ } ) ;
1705+ } else {
1706+ pendingInteractionMap . set ( expirationTime , new Set ( interactions ) ) ;
1707+
1708+ // Update the pending async work count for the current interactions.
1709+ interactions . forEach ( interaction => {
1710+ interaction . __count ++ ;
1711+ } ) ;
1712+ }
1713+
1714+ const subscriber = __subscriberRef . current ;
1715+ if ( subscriber !== null ) {
1716+ const threadID = computeThreadID (
1717+ expirationTime ,
1718+ root . interactionThreadID ,
1719+ ) ;
1720+ subscriber . onWorkScheduled ( interactions , threadID ) ;
1721+ }
1722+ }
17461723 }
17471724
17481725 if (
@@ -1935,9 +1912,8 @@ function onTimeout(root, finishedWork, suspendedExpirationTime) {
19351912 if ( enableSchedulerTracing ) {
19361913 // Don't update pending interaction counts for suspense timeouts,
19371914 // Because we know we still need to do more work in this case.
1938- suspenseDidTimeout = true ;
1915+ interactionsHaveBeenSuspended = true ;
19391916 flushRoot ( root , suspendedExpirationTime ) ;
1940- suspenseDidTimeout = false ;
19411917 } else {
19421918 flushRoot ( root , suspendedExpirationTime ) ;
19431919 }
@@ -2508,6 +2484,12 @@ function flushControlled(fn: () => mixed): void {
25082484 }
25092485}
25102486
2487+ function captureWillSyncRenderPlaceholder ( ) {
2488+ if ( enableSchedulerTracing ) {
2489+ interactionsHaveBeenSuspended = true ;
2490+ }
2491+ }
2492+
25112493export {
25122494 requestCurrentTime ,
25132495 computeExpirationForFiber,
@@ -2530,4 +2512,5 @@ export {
25302512 interactiveUpdates,
25312513 flushInteractiveUpdates,
25322514 computeUniqueAsyncExpiration,
2515+ captureWillSyncRenderPlaceholder,
25332516} ;
0 commit comments