Skip to content

Commit 55213cc

Browse files
committed
Add infinite update loop detection (#28279)
This is a partial redo of facebook/react#26625. Since that was unlanded due to some detected breakages. This now includes a feature flag to be careful in rolling this out. DiffTrain build for commit facebook/react@d8c1fa6.
1 parent 2ad4616 commit 55213cc

File tree

13 files changed

+509
-244
lines changed

13 files changed

+509
-244
lines changed

compiled-rn/facebook-fbsource/xplat/js/RKJSModules/vendor/react-test-renderer/cjs/ReactTestRenderer-dev.js

Lines changed: 55 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@
77
* @noflow
88
* @nolint
99
* @preventMunge
10-
* @generated SignedSource<<491e433a3e81760180afd88ca49b58ec>>
10+
* @generated SignedSource<<1aed0b993a4c164c11bd44255ec4ddac>>
1111
*/
1212

1313
"use strict";
@@ -1728,7 +1728,7 @@ if (__DEV__) {
17281728

17291729
return laneMap;
17301730
}
1731-
function markRootUpdated(root, updateLane) {
1731+
function markRootUpdated$1(root, updateLane) {
17321732
root.pendingLanes |= updateLane; // If there are any suspended transitions, it's possible this new update
17331733
// could unblock them. Clear the suspended lanes so that we can try rendering
17341734
// them again.
@@ -1765,7 +1765,7 @@ if (__DEV__) {
17651765
markSpawnedDeferredLane(root, spawnedLane, suspendedLanes);
17661766
}
17671767
}
1768-
function markRootPinged(root, pingedLanes) {
1768+
function markRootPinged$1(root, pingedLanes) {
17691769
root.pingedLanes |= root.suspendedLanes & pingedLanes;
17701770
}
17711771
function markRootFinished(root, remainingLanes, spawnedLane) {
@@ -21608,7 +21608,9 @@ if (__DEV__) {
2160821608
var workInProgressRootConcurrentErrors = null; // These are errors that we recovered from without surfacing them to the UI.
2160921609
// We will log them once the tree commits.
2161021610

21611-
var workInProgressRootRecoverableErrors = null; // The most recent time we either committed a fallback, or when a fallback was
21611+
var workInProgressRootRecoverableErrors = null; // Tracks when an update occurs during the render phase.
21612+
21613+
var workInProgressRootDidIncludeRecursiveRenderUpdate = false; // Thacks when an update occurs during the commit phase. It's a separate
2161221614
// filled in with the resolved UI. This lets us throttle the appearance of new
2161321615
// content as it streams in, to minimize jank.
2161421616
// TODO: Think of a better name for this variable?
@@ -22118,6 +22120,7 @@ if (__DEV__) {
2211822120
root,
2211922121
workInProgressRootRecoverableErrors,
2212022122
workInProgressTransitions,
22123+
workInProgressRootDidIncludeRecursiveRenderUpdate,
2212122124
workInProgressDeferredLane
2212222125
);
2212322126
} else {
@@ -22148,6 +22151,7 @@ if (__DEV__) {
2214822151
finishedWork,
2214922152
workInProgressRootRecoverableErrors,
2215022153
workInProgressTransitions,
22154+
workInProgressRootDidIncludeRecursiveRenderUpdate,
2215122155
lanes,
2215222156
workInProgressDeferredLane
2215322157
),
@@ -22162,6 +22166,7 @@ if (__DEV__) {
2216222166
finishedWork,
2216322167
workInProgressRootRecoverableErrors,
2216422168
workInProgressTransitions,
22169+
workInProgressRootDidIncludeRecursiveRenderUpdate,
2216522170
lanes,
2216622171
workInProgressDeferredLane
2216722172
);
@@ -22173,6 +22178,7 @@ if (__DEV__) {
2217322178
finishedWork,
2217422179
recoverableErrors,
2217522180
transitions,
22181+
didIncludeRenderPhaseUpdate,
2217622182
lanes,
2217722183
spawnedLane
2217822184
) {
@@ -22197,14 +22203,26 @@ if (__DEV__) {
2219722203
// us that it's ready. This will be canceled if we start work on the
2219822204
// root again.
2219922205
root.cancelPendingCommit = schedulePendingCommit(
22200-
commitRoot.bind(null, root, recoverableErrors, transitions)
22206+
commitRoot.bind(
22207+
null,
22208+
root,
22209+
recoverableErrors,
22210+
transitions,
22211+
didIncludeRenderPhaseUpdate
22212+
)
2220122213
);
2220222214
markRootSuspended(root, lanes, spawnedLane);
2220322215
return;
2220422216
}
2220522217
} // Otherwise, commit immediately.
2220622218

22207-
commitRoot(root, recoverableErrors, transitions, spawnedLane);
22219+
commitRoot(
22220+
root,
22221+
recoverableErrors,
22222+
transitions,
22223+
didIncludeRenderPhaseUpdate,
22224+
spawnedLane
22225+
);
2220822226
}
2220922227

2221022228
function isRenderConsistentWithExternalStores(finishedWork) {
@@ -22267,13 +22285,23 @@ if (__DEV__) {
2226722285
// eslint-disable-next-line no-unreachable
2226822286

2226922287
return true;
22288+
} // The extra indirections around markRootUpdated and markRootSuspended is
22289+
// needed to avoid a circular dependency between this module and
22290+
// ReactFiberLane. There's probably a better way to split up these modules and
22291+
// avoid this problem. Perhaps all the root-marking functions should move into
22292+
// the work loop.
22293+
22294+
function markRootUpdated(root, updatedLanes) {
22295+
markRootUpdated$1(root, updatedLanes);
22296+
}
22297+
22298+
function markRootPinged(root, pingedLanes) {
22299+
markRootPinged$1(root, pingedLanes);
2227022300
}
2227122301

2227222302
function markRootSuspended(root, suspendedLanes, spawnedLane) {
2227322303
// When suspending, we should always exclude lanes that were pinged or (more
2227422304
// rarely, since we try to avoid it) updated during the render phase.
22275-
// TODO: Lol maybe there's a better way to factor this besides this
22276-
// obnoxiously named function :)
2227722305
suspendedLanes = removeLanes(
2227822306
suspendedLanes,
2227922307
workInProgressRootPingedLanes
@@ -22282,6 +22310,7 @@ if (__DEV__) {
2228222310
suspendedLanes,
2228322311
workInProgressRootInterleavedUpdatedLanes
2228422312
);
22313+
2228522314
markRootSuspended$1(root, suspendedLanes, spawnedLane);
2228622315
} // This is the entry point for synchronous tasks that don't go
2228722316
// through Scheduler
@@ -22356,6 +22385,7 @@ if (__DEV__) {
2235622385
root,
2235722386
workInProgressRootRecoverableErrors,
2235822387
workInProgressTransitions,
22388+
workInProgressRootDidIncludeRecursiveRenderUpdate,
2235922389
workInProgressDeferredLane
2236022390
); // Before exiting, make sure there's a callback scheduled for the next
2236122391
// pending level.
@@ -22500,7 +22530,8 @@ if (__DEV__) {
2250022530
workInProgressRootPingedLanes = NoLanes;
2250122531
workInProgressDeferredLane = NoLane;
2250222532
workInProgressRootConcurrentErrors = null;
22503-
workInProgressRootRecoverableErrors = null; // Get the lanes that are entangled with whatever we're about to render. We
22533+
workInProgressRootRecoverableErrors = null;
22534+
workInProgressRootDidIncludeRecursiveRenderUpdate = false; // Get the lanes that are entangled with whatever we're about to render. We
2250422535
// track these separately so we can distinguish the priority of the render
2250522536
// task from the priority of the lanes it is entangled with. For example, a
2250622537
// transition may not be allowed to finish unless it includes the Sync lane,
@@ -23450,7 +23481,13 @@ if (__DEV__) {
2345023481
workInProgress = null;
2345123482
}
2345223483

23453-
function commitRoot(root, recoverableErrors, transitions, spawnedLane) {
23484+
function commitRoot(
23485+
root,
23486+
recoverableErrors,
23487+
transitions,
23488+
didIncludeRenderPhaseUpdate,
23489+
spawnedLane
23490+
) {
2345423491
// TODO: This no longer makes any sense. We already wrap the mutation and
2345523492
// layout phases. Should be able to remove.
2345623493
var previousUpdateLanePriority = getCurrentUpdatePriority();
@@ -23463,6 +23500,7 @@ if (__DEV__) {
2346323500
root,
2346423501
recoverableErrors,
2346523502
transitions,
23503+
didIncludeRenderPhaseUpdate,
2346623504
previousUpdateLanePriority,
2346723505
spawnedLane
2346823506
);
@@ -23478,6 +23516,7 @@ if (__DEV__) {
2347823516
root,
2347923517
recoverableErrors,
2348023518
transitions,
23519+
didIncludeRenderPhaseUpdate,
2348123520
renderPriorityLevel,
2348223521
spawnedLane
2348323522
) {
@@ -23537,7 +23576,7 @@ if (__DEV__) {
2353723576

2353823577
var concurrentlyUpdatedLanes = getConcurrentlyUpdatedLanes();
2353923578
remainingLanes = mergeLanes(remainingLanes, concurrentlyUpdatedLanes);
23540-
markRootFinished(root, remainingLanes, spawnedLane);
23579+
markRootFinished(root, remainingLanes, spawnedLane); // Reset this before firing side effects so we can detect recursive updates.
2354123580

2354223581
if (root === workInProgressRoot) {
2354323582
// We can reset these now that they are finished.
@@ -23725,6 +23764,9 @@ if (__DEV__) {
2372523764
// hydration is conceptually not an update.
2372623765

2372723766
if (
23767+
// Check if there was a recursive update spawned by this render, in either
23768+
// the render phase or the commit phase. We track these explicitly because
23769+
// we can't infer from the remaining lanes alone.
2372823770
// Was the finished render the result of an update (not hydration)?
2372923771
includesSomeLane(lanes, UpdateLanes) && // Did it schedule a sync update?
2373023772
includesSomeLane(remainingLanes, SyncUpdateLanes)
@@ -24172,6 +24214,7 @@ if (__DEV__) {
2417224214
nestedPassiveUpdateCount = 0;
2417324215
rootWithNestedUpdates = null;
2417424216
rootWithPassiveNestedUpdates = null;
24217+
2417524218
throw new Error(
2417624219
"Maximum update depth exceeded. This can happen when a component " +
2417724220
"repeatedly calls setState inside componentWillUpdate or " +
@@ -25678,7 +25721,7 @@ if (__DEV__) {
2567825721
return root;
2567925722
}
2568025723

25681-
var ReactVersion = "18.3.0-canary-03d6f7cf0-20240209";
25724+
var ReactVersion = "18.3.0-canary-d8c1fa6b0-20240209";
2568225725

2568325726
// Might add PROFILE later.
2568425727

0 commit comments

Comments
 (0)