90
SimpleMemoComponent,
91
Block,
92
} from 'shared/ReactWorkTags';
93
+import {LegacyRoot} from 'shared/ReactRootTags';
94
import {
95
NoEffect,
96
PerformedWork,
644
// event time. The next update will compute a new event time.
645
currentEventTime = NoWork;
646
647
+ // Check if the render expired.
648
if (didTimeout) {
649
// The render task took too long to complete. Mark the current time as
650
// expired to synchronously render all expired work in a single batch.
657
658
// Determine the next expiration time to work on, using the fields stored
659
// on the root.
658
- const expirationTime = getNextRootExpirationTimeToWorkOn(root);
659
- if (expirationTime !== NoWork) {
660
- const originalCallbackNode = root.callbackNode;
661
- invariant(
662
- (executionContext & (RenderContext | CommitContext)) === NoContext,
663
- 'Should not already be working.',
664
- );
665
-
666
- flushPassiveEffects();
660
+ let expirationTime = getNextRootExpirationTimeToWorkOn(root);
661
+ if (expirationTime === NoWork) {
662
+ return null;
663
+ }
664
+ const originalCallbackNode = root.callbackNode;
665
+ invariant(
666
+ (executionContext & (RenderContext | CommitContext)) === NoContext,
667
+ 'Should not already be working.',
668
+ );
669
668
- // If the root or expiration time have changed, throw out the existing stack
669
- // and prepare a fresh one. Otherwise we'll continue where we left off.
670
- if (
671
- root !== workInProgressRoot ||
672
- expirationTime !== renderExpirationTime
673
- ) {
674
- prepareFreshStack(root, expirationTime);
675
- startWorkOnPendingInteractions(root, expirationTime);
676
- }
677
-
678
- // If we have a work-in-progress fiber, it means there's still work to do
679
- // in this root.
680
- if (workInProgress !== null) {
681
- const prevExecutionContext = executionContext;
682
- executionContext |= RenderContext;
683
- const prevDispatcher = pushDispatcher(root);
684
- const prevInteractions = pushInteractions(root);
685
- startWorkLoopTimer(workInProgress);
686
- do {
687
- try {
688
- workLoopConcurrent();
689
- break;
690
- } catch (thrownValue) {
691
- handleError(root, thrownValue);
692
- }
693
- } while (true);
694
- resetContextDependencies();
695
- executionContext = prevExecutionContext;
696
- popDispatcher(prevDispatcher);
697
- if (enableSchedulerTracing) {
698
- popInteractions(((prevInteractions: any): Set<Interaction>));
699
- }
670
+ flushPassiveEffects();
671
701
- if (workInProgressRootExitStatus === RootFatalErrored) {
702
- const fatalError = workInProgressRootFatalError;
703
- stopInterruptedWorkLoopTimer();
704
- prepareFreshStack(root, expirationTime);
705
- markRootSuspendedAtTime(root, expirationTime);
706
- ensureRootIsScheduled(root);
707
- throw fatalError;
708
- }
672
+ let exitStatus = renderRootConcurrent(root, expirationTime);
673
710
- if (workInProgress !== null) {
711
- // There's still work left over. Exit without committing.
712
- stopInterruptedWorkLoopTimer();
713
- } else {
714
- // We now have a consistent tree. The next step is either to commit it,
715
- // or, if something suspended, wait to commit it after a timeout.
716
- stopFinishedWorkLoopTimer();
717
-
718
- const finishedWork: Fiber = ((root.finishedWork =
719
- root.current.alternate): any);
720
- root.finishedExpirationTime = expirationTime;
721
- finishConcurrentRender(
722
- root,
723
- finishedWork,
724
- workInProgressRootExitStatus,
725
- expirationTime,
726
- );
727
- }
674
+ if (exitStatus !== RootIncomplete) {
675
+ if (exitStatus === RootErrored) {
676
+ // If something threw an error, try rendering one more time. We'll
677
+ // render synchronously to block concurrent data mutations, and we'll
678
+ // render at Idle (or lower) so that all pending updates are included.
679
+ // If it still fails after the second attempt, we'll give up and commit
680
+ // the resulting tree.
681
+ expirationTime = expirationTime > Idle ? Idle : expirationTime;
682
+ exitStatus = renderRootSync(root, expirationTime);
683
+ }
684
685
+ if (exitStatus === RootFatalErrored) {
686
+ const fatalError = workInProgressRootFatalError;
687
+ prepareFreshStack(root, expirationTime);
688
+ markRootSuspendedAtTime(root, expirationTime);
689
ensureRootIsScheduled(root);
730
- if (root.callbackNode === originalCallbackNode) {
731
- // The task node scheduled for this root is the same one that's
732
- // currently executed. Need to return a continuation.
733
- return performConcurrentWorkOnRoot.bind(null, root);
734
- }
690
+ throw fatalError;
691
}
692
+
693
+ // We now have a consistent tree. The next step is either to commit it,
694
+ // or, if something suspended, wait to commit it after a timeout.
695
+ const finishedWork: Fiber = ((root.finishedWork =
696
+ root.current.alternate): any);
697
+ root.finishedExpirationTime = expirationTime;
698
+ finishConcurrentRender(root, finishedWork, exitStatus, expirationTime);
699
+ }
700
+
701
+ ensureRootIsScheduled(root);
702
+ if (root.callbackNode === originalCallbackNode) {
703
+ // The task node scheduled for this root is the same one that's
704
+ // currently executed. Need to return a continuation.
705
+ return performConcurrentWorkOnRoot.bind(null, root);
706
}
707
return null;
708
}
713
exitStatus,
714
expirationTime,
715
) {
746
- // Set this to null to indicate there's no in-progress render.
747
- workInProgressRoot = null;
748
-
716
switch (exitStatus) {
717
case RootIncomplete:
718
case RootFatalErrored: {
722
// statement, but eslint doesn't know about invariant, so it complains
723
// if I do. eslint-disable-next-line no-fallthrough
724
case RootErrored: {
758
- // If this was an async render, the error may have happened due to
759
- // a mutation in a concurrent event. Try rendering one more time,
760
- // synchronously, to see if the error goes away. If there are
761
- // lower priority updates, let's include those, too, in case they
762
- // fix the inconsistency. Render at Idle to include all updates.
763
- // If it was Idle or Never or some not-yet-invented time, render
764
- // at that time.
765
- markRootExpiredAtTime(
766
- root,
767
- expirationTime > Idle ? Idle : expirationTime,
768
- );
769
- // We assume that this second render pass will be synchronous
770
- // and therefore not hit this path again.
725
+ // We should have already attempted to retry this tree. If we reached
726
+ // this point, it errored again. Commit it.
727
+ commitRoot(root);
728
break;
729
}
730
case RootSuspended: {
940
// This is the entry point for synchronous tasks that don't go
941
// through Scheduler
942
function performSyncWorkOnRoot(root) {
986
- // Check if there's expired work on this root. Otherwise, render at Sync.
987
- const lastExpiredTime = root.lastExpiredTime;
988
- const expirationTime = lastExpiredTime !== NoWork ? lastExpiredTime : Sync;
943
invariant(
944
(executionContext & (RenderContext | CommitContext)) === NoContext,
945
'Should not already be working.',
947
948
flushPassiveEffects();
949
996
- // If the root or expiration time have changed, throw out the existing stack
997
- // and prepare a fresh one. Otherwise we'll continue where we left off.
998
- if (root !== workInProgressRoot || expirationTime !== renderExpirationTime) {
999
- prepareFreshStack(root, expirationTime);
1000
- startWorkOnPendingInteractions(root, expirationTime);
1001
- }
1002
-
1003
- // If we have a work-in-progress fiber, it means there's still work to do
1004
- // in this root.
1005
- if (workInProgress !== null) {
1006
- const prevExecutionContext = executionContext;
1007
- executionContext |= RenderContext;
1008
- const prevDispatcher = pushDispatcher(root);
1009
- const prevInteractions = pushInteractions(root);
1010
- startWorkLoopTimer(workInProgress);
950
+ const lastExpiredTime = root.lastExpiredTime;
951
1012
- do {
1013
- try {
1014
- workLoopSync();
1015
- break;
1016
- } catch (thrownValue) {
1017
- handleError(root, thrownValue);
1018
- }
1019
- } while (true);
1020
- resetContextDependencies();
1021
- executionContext = prevExecutionContext;
1022
- popDispatcher(prevDispatcher);
1023
- if (enableSchedulerTracing) {
1024
- popInteractions(((prevInteractions: any): Set<Interaction>));
952
+ let expirationTime;
953
+ if (lastExpiredTime !== NoWork) {
954
+ // There's expired work on this root. Check if we have a partial tree
955
+ // that we can reuse.
956
+ if (
957
+ root === workInProgressRoot &&
958
+ renderExpirationTime >= lastExpiredTime
959
+ ) {
960
+ // There's a partial tree with equal or greater than priority than the
961
+ // expired level. Finish rendering it before rendering the rest of the
962
+ // expired work.
963
+ expirationTime = renderExpirationTime;
964
+ } else {
965
+ // Start a fresh tree.
966
+ expirationTime = lastExpiredTime;
967
}
968
+ } else {
969
+ // There's no expired work. This must be a new, synchronous render.
970
+ expirationTime = Sync;
971
+ }
972
1027
- if (workInProgressRootExitStatus === RootFatalErrored) {
1028
- const fatalError = workInProgressRootFatalError;
1029
- stopInterruptedWorkLoopTimer();
1030
- prepareFreshStack(root, expirationTime);
1031
- markRootSuspendedAtTime(root, expirationTime);
1032
- ensureRootIsScheduled(root);
1033
- throw fatalError;
1034
- }
973
+ let exitStatus = renderRootSync(root, expirationTime);
974
1036
- if (workInProgress !== null) {
1037
- // This is a sync render, so we should have finished the whole tree.
1038
- invariant(
1039
- false,
1040
- 'Cannot commit an incomplete root. This error is likely caused by a ' +
1041
- 'bug in React. Please file an issue.',
1042
- );
1043
- } else {
1044
- // We now have a consistent tree. Because this is a sync render, we
1045
- // will commit it even if something suspended.
1046
- stopFinishedWorkLoopTimer();
1047
- root.finishedWork = (root.current.alternate: any);
1048
- root.finishedExpirationTime = expirationTime;
1049
- finishSyncRender(root);
1050
- }
975
+ if (root.tag !== LegacyRoot && exitStatus === RootErrored) {
976
+ // If something threw an error, try rendering one more time. We'll
977
+ // render synchronously to block concurrent data mutations, and we'll
978
+ // render at Idle (or lower) so that all pending updates are included.
979
+ // If it still fails after the second attempt, we'll give up and commit
980
+ // the resulting tree.
981
+ expirationTime = expirationTime > Idle ? Idle : expirationTime;
982
+ exitStatus = renderRootSync(root, expirationTime);
983
+ }
984
1052
- // Before exiting, make sure there's a callback scheduled for the next
1053
- // pending level.
985
+ if (exitStatus === RootFatalErrored) {
986
+ const fatalError = workInProgressRootFatalError;
987
+ prepareFreshStack(root, expirationTime);
988
+ markRootSuspendedAtTime(root, expirationTime);
989
ensureRootIsScheduled(root);
990
+ throw fatalError;
991
}
992
1057
- return null;
1058
-}
993
+ // We now have a consistent tree. Because this is a sync render, we
994
+ // will commit it even if something suspended.
995
+ root.finishedWork = (root.current.alternate: any);
996
+ root.finishedExpirationTime = expirationTime;
997
1060
-function finishSyncRender(root) {
1061
- // Set this to null to indicate there's no in-progress render.
1062
- workInProgressRoot = null;
998
commitRoot(root);
999
+
1000
+ // Before exiting, make sure there's a callback scheduled for the next
1001
+ // pending level.
1002
+ ensureRootIsScheduled(root);
1003
+
1004
+ return null;
1005
}
1006
1007
export function flushRoot(root: FiberRoot, expirationTime: ExpirationTime) {
1390
);
1391
}
1392
1393
+function renderRootSync(root, expirationTime) {
1394
+ const prevExecutionContext = executionContext;
1395
+ executionContext |= RenderContext;
1396
+ const prevDispatcher = pushDispatcher(root);
1397
+
1398
+ // If the root or expiration time have changed, throw out the existing stack
1399
+ // and prepare a fresh one. Otherwise we'll continue where we left off.
1400
+ if (root !== workInProgressRoot || expirationTime !== renderExpirationTime) {
1401
+ prepareFreshStack(root, expirationTime);
1402
+ startWorkOnPendingInteractions(root, expirationTime);
1403
+ }
1404
+
1405
+ const prevInteractions = pushInteractions(root);
1406
+ startWorkLoopTimer(workInProgress);
1407
+ do {
1408
+ try {
1409
+ workLoopSync();
1410
+ break;
1411
+ } catch (thrownValue) {
1412
+ handleError(root, thrownValue);
1413
+ }
1414
+ } while (true);
1415
+ resetContextDependencies();
1416
+ if (enableSchedulerTracing) {
1417
+ popInteractions(((prevInteractions: any): Set<Interaction>));
1418
+ }
1419
+
1420
+ executionContext = prevExecutionContext;
1421
+ popDispatcher(prevDispatcher);
1422
+
1423
+ if (workInProgress !== null) {
1424
+ // This is a sync render, so we should have finished the whole tree.
1425
+ invariant(
1426
+ false,
1427
+ 'Cannot commit an incomplete root. This error is likely caused by a ' +
1428
+ 'bug in React. Please file an issue.',
1429
+ );
1430
+ }
1431
+
1432
+ stopFinishedWorkLoopTimer();
1433
+
1434
+ // Set this to null to indicate there's no in-progress render.
1435
+ workInProgressRoot = null;
1436
+
1437
+ return workInProgressRootExitStatus;
1438
+}
1439
+
1440
// The work loop is an extremely hot path. Tell Closure not to inline it.
1441
/** @noinline */
1442
function workLoopSync() {
1446
}
1447
}
1448
1449
+function renderRootConcurrent(root, expirationTime) {
1450
+ const prevExecutionContext = executionContext;
1451
+ executionContext |= RenderContext;
1452
+ const prevDispatcher = pushDispatcher(root);
1453
+
1454
+ // If the root or expiration time have changed, throw out the existing stack
1455
+ // and prepare a fresh one. Otherwise we'll continue where we left off.
1456
+ if (root !== workInProgressRoot || expirationTime !== renderExpirationTime) {
1457
+ prepareFreshStack(root, expirationTime);
1458
+ startWorkOnPendingInteractions(root, expirationTime);
1459
+ }
1460
+
1461
+ const prevInteractions = pushInteractions(root);
1462
+ startWorkLoopTimer(workInProgress);
1463
+ do {
1464
+ try {
1465
+ workLoopConcurrent();
1466
+ break;
1467
+ } catch (thrownValue) {
1468
+ handleError(root, thrownValue);
1469
+ }
1470
+ } while (true);
1471
+ resetContextDependencies();
1472
+ if (enableSchedulerTracing) {
1473
+ popInteractions(((prevInteractions: any): Set<Interaction>));
1474
+ }
1475
+
1476
+ popDispatcher(prevDispatcher);
1477
+ executionContext = prevExecutionContext;
1478
+
1479
+ // Check if the tree has completed.
1480
+ if (workInProgress !== null) {
1481
+ // Still work remaining.
1482
+ stopInterruptedWorkLoopTimer();
1483
+ return RootIncomplete;
1484
+ } else {
1485
+ // Completed the tree.
1486
+ stopFinishedWorkLoopTimer();
1487
+
1488
+ // Set this to null to indicate there's no in-progress render.
1489
+ workInProgressRoot = null;
1490
+
1491
+ // Return the final exit status.
1492
+ return workInProgressRootExitStatus;
1493
+ }
1494
+}
1495
+
1496
/** @noinline */
1497
function workLoopConcurrent() {
1498
// Perform work until Scheduler asks us to yield