7
7
* @noflow
8
8
* @nolint
9
9
* @preventMunge
10
- * @generated SignedSource<<bfa37b9235cbe10193422349b5fb18a5 >>
10
+ * @generated SignedSource<<658af937953acc877e6c9c23bdafbf60 >>
11
11
*/
12
12
13
13
'use strict';
@@ -1584,7 +1584,7 @@ function createLaneMap(initial) {
1584
1584
1585
1585
return laneMap;
1586
1586
}
1587
- function markRootUpdated(root, updateLane) {
1587
+ function markRootUpdated$1 (root, updateLane) {
1588
1588
root.pendingLanes |= updateLane; // If there are any suspended transitions, it's possible this new update
1589
1589
// could unblock them. Clear the suspended lanes so that we can try rendering
1590
1590
// them again.
@@ -1617,7 +1617,7 @@ function markRootSuspended$1(root, suspendedLanes) {
1617
1617
lanes &= ~lane;
1618
1618
}
1619
1619
}
1620
- function markRootPinged(root, pingedLanes) {
1620
+ function markRootPinged$1 (root, pingedLanes) {
1621
1621
root.pingedLanes |= root.suspendedLanes & pingedLanes;
1622
1622
}
1623
1623
function markRootFinished(root, remainingLanes) {
@@ -6004,6 +6004,21 @@ function ensureRootIsScheduled(root) {
6004
6004
ReactCurrentActQueue$2.didScheduleLegacyUpdate = true;
6005
6005
}
6006
6006
}
6007
+
6008
+ function unscheduleAllRoots() {
6009
+ // This is only done in a fatal error situation, as a last resort to prevent
6010
+ // an infinite render loop.
6011
+ var root = firstScheduledRoot;
6012
+
6013
+ while (root !== null) {
6014
+ var next = root.next;
6015
+ root.next = null;
6016
+ root = next;
6017
+ }
6018
+
6019
+ firstScheduledRoot = lastScheduledRoot = null;
6020
+ }
6021
+
6007
6022
function flushSyncWorkOnAllRoots() {
6008
6023
// This is allowed to be called synchronously, but the caller should check
6009
6024
// the execution context first.
@@ -6032,11 +6047,49 @@ function flushSyncWorkAcrossRoots_impl(onlyLegacy) {
6032
6047
var workInProgressRootRenderLanes = getWorkInProgressRootRenderLanes(); // There may or may not be synchronous work scheduled. Let's check.
6033
6048
6034
6049
var didPerformSomeWork;
6050
+ var nestedUpdatePasses = 0;
6035
6051
var errors = null;
6036
6052
isFlushingWork = true;
6037
6053
6038
6054
do {
6039
- didPerformSomeWork = false;
6055
+ didPerformSomeWork = false; // This outer loop re-runs if performing sync work on a root spawns
6056
+ // additional sync work. If it happens too many times, it's very likely
6057
+ // caused by some sort of infinite update loop. We already have a loop guard
6058
+ // in place that will trigger an error on the n+1th update, but it's
6059
+ // possible for that error to get swallowed if the setState is called from
6060
+ // an unexpected place, like during the render phase. So as an added
6061
+ // precaution, we also use a guard here.
6062
+ //
6063
+ // Ideally, there should be no known way to trigger this synchronous loop.
6064
+ // It's really just here as a safety net.
6065
+ //
6066
+ // This limit is slightly larger than the one that throws inside setState,
6067
+ // because that one is preferable because it includes a componens stack.
6068
+
6069
+ if (++nestedUpdatePasses > 60) {
6070
+ // This is a fatal error, so we'll unschedule all the roots.
6071
+ unscheduleAllRoots(); // TODO: Change this error message to something different to distinguish
6072
+ // it from the one that is thrown from setState. Those are less fatal
6073
+ // because they usually will result in the bad component being unmounted,
6074
+ // and an error boundary being triggered, rather than us having to
6075
+ // forcibly stop the entire scheduler.
6076
+
6077
+ var infiniteUpdateError = new Error(
6078
+ "Maximum update depth exceeded. This can happen when a component " +
6079
+ "repeatedly calls setState inside componentWillUpdate or " +
6080
+ "componentDidUpdate. React limits the number of nested updates to " +
6081
+ "prevent infinite loops."
6082
+ );
6083
+
6084
+ if (errors === null) {
6085
+ errors = [infiniteUpdateError];
6086
+ } else {
6087
+ errors.push(infiniteUpdateError);
6088
+ }
6089
+
6090
+ break;
6091
+ }
6092
+
6040
6093
var root = firstScheduledRoot;
6041
6094
6042
6095
while (root !== null) {
@@ -19938,7 +19991,13 @@ var workInProgressRootPingedLanes = NoLanes; // Errors that are thrown during th
19938
19991
var workInProgressRootConcurrentErrors = null; // These are errors that we recovered from without surfacing them to the UI.
19939
19992
// We will log them once the tree commits.
19940
19993
19941
- var workInProgressRootRecoverableErrors = null; // The most recent time we either committed a fallback, or when a fallback was
19994
+ var workInProgressRootRecoverableErrors = null; // Tracks when an update occurs during the render phase.
19995
+
19996
+ var workInProgressRootDidIncludeRecursiveRenderUpdate = false; // Thacks when an update occurs during the commit phase. It's a separate
19997
+ // variable from the one for renders because the commit phase may run
19998
+ // concurrently to a render phase.
19999
+
20000
+ var didIncludeCommitPhaseUpdate = false; // The most recent time we either committed a fallback, or when a fallback was
19942
20001
// filled in with the resolved UI. This lets us throttle the appearance of new
19943
20002
// content as it streams in, to minimize jank.
19944
20003
// TODO: Think of a better name for this variable?
@@ -20420,7 +20479,8 @@ function finishConcurrentRender(root, exitStatus, finishedWork, lanes) {
20420
20479
commitRoot(
20421
20480
root,
20422
20481
workInProgressRootRecoverableErrors,
20423
- workInProgressTransitions
20482
+ workInProgressTransitions,
20483
+ workInProgressRootDidIncludeRecursiveRenderUpdate
20424
20484
);
20425
20485
} else {
20426
20486
if (includesOnlyRetries(lanes) && alwaysThrottleRetries) {
@@ -20450,6 +20510,7 @@ function finishConcurrentRender(root, exitStatus, finishedWork, lanes) {
20450
20510
finishedWork,
20451
20511
workInProgressRootRecoverableErrors,
20452
20512
workInProgressTransitions,
20513
+ workInProgressRootDidIncludeRecursiveRenderUpdate,
20453
20514
lanes
20454
20515
),
20455
20516
msUntilTimeout
@@ -20463,6 +20524,7 @@ function finishConcurrentRender(root, exitStatus, finishedWork, lanes) {
20463
20524
finishedWork,
20464
20525
workInProgressRootRecoverableErrors,
20465
20526
workInProgressTransitions,
20527
+ workInProgressRootDidIncludeRecursiveRenderUpdate,
20466
20528
lanes
20467
20529
);
20468
20530
}
@@ -20473,6 +20535,7 @@ function commitRootWhenReady(
20473
20535
finishedWork,
20474
20536
recoverableErrors,
20475
20537
transitions,
20538
+ didIncludeRenderPhaseUpdate,
20476
20539
lanes
20477
20540
) {
20478
20541
// TODO: Combine retry throttling with Suspensey commits. Right now they run
@@ -20496,14 +20559,20 @@ function commitRootWhenReady(
20496
20559
// us that it's ready. This will be canceled if we start work on the
20497
20560
// root again.
20498
20561
root.cancelPendingCommit = schedulePendingCommit(
20499
- commitRoot.bind(null, root, recoverableErrors, transitions)
20562
+ commitRoot.bind(
20563
+ null,
20564
+ root,
20565
+ recoverableErrors,
20566
+ transitions,
20567
+ didIncludeRenderPhaseUpdate
20568
+ )
20500
20569
);
20501
20570
markRootSuspended(root, lanes);
20502
20571
return;
20503
20572
}
20504
20573
} // Otherwise, commit immediately.
20505
20574
20506
- commitRoot(root, recoverableErrors, transitions);
20575
+ commitRoot(root, recoverableErrors, transitions, didIncludeRenderPhaseUpdate );
20507
20576
}
20508
20577
20509
20578
function isRenderConsistentWithExternalStores(finishedWork) {
@@ -20566,18 +20635,49 @@ function isRenderConsistentWithExternalStores(finishedWork) {
20566
20635
// eslint-disable-next-line no-unreachable
20567
20636
20568
20637
return true;
20638
+ } // The extra indirections around markRootUpdated and markRootSuspended is
20639
+ // needed to avoid a circular dependency between this module and
20640
+ // ReactFiberLane. There's probably a better way to split up these modules and
20641
+ // avoid this problem. Perhaps all the root-marking functions should move into
20642
+ // the work loop.
20643
+
20644
+ function markRootUpdated(root, updatedLanes) {
20645
+ markRootUpdated$1(root, updatedLanes); // Check for recursive updates
20646
+
20647
+ if (executionContext & RenderContext) {
20648
+ workInProgressRootDidIncludeRecursiveRenderUpdate = true;
20649
+ } else if (executionContext & CommitContext) {
20650
+ didIncludeCommitPhaseUpdate = true;
20651
+ }
20652
+
20653
+ throwIfInfiniteUpdateLoopDetected();
20654
+ }
20655
+
20656
+ function markRootPinged(root, pingedLanes) {
20657
+ markRootPinged$1(root, pingedLanes); // Check for recursive pings. Pings are conceptually different from updates in
20658
+ // other contexts but we call it an "update" in this context because
20659
+ // repeatedly pinging a suspended render can cause a recursive render loop.
20660
+ // The relevant property is that it can result in a new render attempt
20661
+ // being scheduled.
20662
+
20663
+ if (executionContext & RenderContext) {
20664
+ workInProgressRootDidIncludeRecursiveRenderUpdate = true;
20665
+ } else if (executionContext & CommitContext) {
20666
+ didIncludeCommitPhaseUpdate = true;
20667
+ }
20668
+
20669
+ throwIfInfiniteUpdateLoopDetected();
20569
20670
}
20570
20671
20571
20672
function markRootSuspended(root, suspendedLanes) {
20572
20673
// When suspending, we should always exclude lanes that were pinged or (more
20573
20674
// rarely, since we try to avoid it) updated during the render phase.
20574
- // TODO: Lol maybe there's a better way to factor this besides this
20575
- // obnoxiously named function :)
20576
20675
suspendedLanes = removeLanes(suspendedLanes, workInProgressRootPingedLanes);
20577
20676
suspendedLanes = removeLanes(
20578
20677
suspendedLanes,
20579
20678
workInProgressRootInterleavedUpdatedLanes
20580
20679
);
20680
+
20581
20681
markRootSuspended$1(root, suspendedLanes);
20582
20682
} // This is the entry point for synchronous tasks that don't go
20583
20683
// through Scheduler
@@ -20648,7 +20748,8 @@ function performSyncWorkOnRoot(root) {
20648
20748
commitRoot(
20649
20749
root,
20650
20750
workInProgressRootRecoverableErrors,
20651
- workInProgressTransitions
20751
+ workInProgressTransitions,
20752
+ workInProgressRootDidIncludeRecursiveRenderUpdate
20652
20753
); // Before exiting, make sure there's a callback scheduled for the next
20653
20754
// pending level.
20654
20755
@@ -20789,6 +20890,7 @@ function prepareFreshStack(root, lanes) {
20789
20890
workInProgressRootPingedLanes = NoLanes;
20790
20891
workInProgressRootConcurrentErrors = null;
20791
20892
workInProgressRootRecoverableErrors = null;
20893
+ workInProgressRootDidIncludeRecursiveRenderUpdate = false;
20792
20894
finishQueueingConcurrentUpdates();
20793
20895
20794
20896
{
@@ -21685,7 +21787,12 @@ function unwindUnitOfWork(unitOfWork) {
21685
21787
workInProgress = null;
21686
21788
}
21687
21789
21688
- function commitRoot(root, recoverableErrors, transitions) {
21790
+ function commitRoot(
21791
+ root,
21792
+ recoverableErrors,
21793
+ transitions,
21794
+ didIncludeRenderPhaseUpdate
21795
+ ) {
21689
21796
// TODO: This no longer makes any sense. We already wrap the mutation and
21690
21797
// layout phases. Should be able to remove.
21691
21798
var previousUpdateLanePriority = getCurrentUpdatePriority();
@@ -21698,6 +21805,7 @@ function commitRoot(root, recoverableErrors, transitions) {
21698
21805
root,
21699
21806
recoverableErrors,
21700
21807
transitions,
21808
+ didIncludeRenderPhaseUpdate,
21701
21809
previousUpdateLanePriority
21702
21810
);
21703
21811
} finally {
@@ -21712,6 +21820,7 @@ function commitRootImpl(
21712
21820
root,
21713
21821
recoverableErrors,
21714
21822
transitions,
21823
+ didIncludeRenderPhaseUpdate,
21715
21824
renderPriorityLevel
21716
21825
) {
21717
21826
do {
@@ -21767,7 +21876,9 @@ function commitRootImpl(
21767
21876
21768
21877
var concurrentlyUpdatedLanes = getConcurrentlyUpdatedLanes();
21769
21878
remainingLanes = mergeLanes(remainingLanes, concurrentlyUpdatedLanes);
21770
- markRootFinished(root, remainingLanes);
21879
+ markRootFinished(root, remainingLanes); // Reset this before firing side effects so we can detect recursive updates.
21880
+
21881
+ didIncludeCommitPhaseUpdate = false;
21771
21882
21772
21883
if (root === workInProgressRoot) {
21773
21884
// We can reset these now that they are finished.
@@ -21948,7 +22059,18 @@ function commitRootImpl(
21948
22059
21949
22060
remainingLanes = root.pendingLanes;
21950
22061
21951
- if (includesSyncLane(remainingLanes)) {
22062
+ if (
22063
+ // Check if there was a recursive update spawned by this render, in either
22064
+ // the render phase or the commit phase. We track these explicitly because
22065
+ // we can't infer from the remaining lanes alone.
22066
+ didIncludeCommitPhaseUpdate ||
22067
+ didIncludeRenderPhaseUpdate || // As an additional precaution, we also check if there's any remaining sync
22068
+ // work. Theoretically this should be unreachable but if there's a mistake
22069
+ // in React it helps to be overly defensive given how hard it is to debug
22070
+ // those scenarios otherwise. This won't catch recursive async updates,
22071
+ // though, which is why we check the flags above first.
22072
+ includesSyncLane(remainingLanes)
22073
+ ) {
21952
22074
{
21953
22075
markNestedUpdateScheduled();
21954
22076
} // Count the number of times the root synchronously re-renders without
@@ -22386,6 +22508,18 @@ function throwIfInfiniteUpdateLoopDetected() {
22386
22508
nestedPassiveUpdateCount = 0;
22387
22509
rootWithNestedUpdates = null;
22388
22510
rootWithPassiveNestedUpdates = null;
22511
+
22512
+ if (executionContext & RenderContext && workInProgressRoot !== null) {
22513
+ // We're in the render phase. Disable the concurrent error recovery
22514
+ // mechanism to ensure that the error we're about to throw gets handled.
22515
+ // We need it to trigger the nearest error boundary so that the infinite
22516
+ // update loop is broken.
22517
+ workInProgressRoot.errorRecoveryDisabledLanes = mergeLanes(
22518
+ workInProgressRoot.errorRecoveryDisabledLanes,
22519
+ workInProgressRootRenderLanes
22520
+ );
22521
+ }
22522
+
22389
22523
throw new Error(
22390
22524
"Maximum update depth exceeded. This can happen when a component " +
22391
22525
"repeatedly calls setState inside componentWillUpdate or " +
@@ -23857,7 +23991,7 @@ function createFiberRoot(
23857
23991
return root;
23858
23992
}
23859
23993
23860
- var ReactVersion = "18.3.0-canary-80d9a4011 -20230627";
23994
+ var ReactVersion = "18.3.0-canary-822386f25 -20230627";
23861
23995
23862
23996
// Might add PROFILE later.
23863
23997
0 commit comments