Skip to content

Commit bdec677

Browse files
committed
Fix: Update while suspended fails to interrupt (#26739)
This fixes a bug with `use` where if you update a component that's currently suspended, React will sometimes mistake it for a render phase update. This happens because we don't reset `currentlyRenderingFiber` until the suspended is unwound. And with `use`, that can happen asynchronously, most commonly when the work loop is suspended during a transition. The fix is to make sure `currentlyRenderingFiber` is only set when we're in the middle of rendering, which used to be true until `use` was introduced. More specifically this means clearing `currentlyRenderingFiber` when something throws and setting it again when we resume work. In many cases, this bug will fail "gracefully" because the update is still added to the queue; it's not dropped completely. It's also somewhat rare because it has to be the exact same component that's currently suspended. But it's still a bug. I wrote a regression test that shows a sync update failing to interrupt a suspended component. DiffTrain build for commit 18282f8.
1 parent fcc18f8 commit bdec677

File tree

13 files changed

+214
-166
lines changed

13 files changed

+214
-166
lines changed

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

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

1313
'use strict';
@@ -6927,6 +6927,7 @@ function renderWithHooksAgain(workInProgress, Component, props, secondArg) {
69276927
//
69286928
// Keep rendering in a loop for as long as render phase updates continue to
69296929
// be scheduled. Use a counter to prevent infinite loops.
6930+
currentlyRenderingFiber$1 = workInProgress;
69306931
var numberOfReRenders = 0;
69316932
var children;
69326933

@@ -6994,11 +6995,12 @@ function resetHooksAfterThrow() {
69946995
//
69956996
// It should only reset things like the current dispatcher, to prevent hooks
69966997
// from being called outside of a component.
6997-
// We can assume the previous dispatcher is always this one, since we set it
6998+
currentlyRenderingFiber$1 = null; // We can assume the previous dispatcher is always this one, since we set it
69986999
// at the beginning of the render phase and there's no re-entrance.
7000+
69997001
ReactCurrentDispatcher$1.current = ContextOnlyDispatcher;
70007002
}
7001-
function resetHooksOnUnwind() {
7003+
function resetHooksOnUnwind(workInProgress) {
70027004
if (didScheduleRenderPhaseUpdate) {
70037005
// There were render phase updates. These are only valid for this render
70047006
// phase, which we are now aborting. Remove the updates from the queues so
@@ -7008,7 +7010,7 @@ function resetHooksOnUnwind() {
70087010
// Only reset the updates from the queue if it has a clone. If it does
70097011
// not have a clone, that means it wasn't processed, and the updates were
70107012
// scheduled before we entered the render phase.
7011-
var hook = currentlyRenderingFiber$1.memoizedState;
7013+
var hook = workInProgress.memoizedState;
70127014

70137015
while (hook !== null) {
70147016
var queue = hook.queue;
@@ -20773,7 +20775,7 @@ function resetWorkInProgressStack() {
2077320775
} else {
2077420776
// Work-in-progress is in suspended state. Reset the work loop and unwind
2077520777
// both the suspended fiber and all its parents.
20776-
resetSuspendedWorkLoopOnUnwind();
20778+
resetSuspendedWorkLoopOnUnwind(workInProgress);
2077720779
interruptedWork = workInProgress;
2077820780
}
2077920781

@@ -20830,10 +20832,10 @@ function prepareFreshStack(root, lanes) {
2083020832
return rootWorkInProgress;
2083120833
}
2083220834

20833-
function resetSuspendedWorkLoopOnUnwind() {
20835+
function resetSuspendedWorkLoopOnUnwind(fiber) {
2083420836
// Reset module-level state that was set during the render phase.
2083520837
resetContextDependencies();
20836-
resetHooksOnUnwind();
20838+
resetHooksOnUnwind(fiber);
2083720839
resetChildReconcilerOnUnwind();
2083820840
}
2083920841

@@ -21488,7 +21490,7 @@ function replaySuspendedUnitOfWork(unitOfWork) {
2148821490
// is to reuse uncached promises, but we happen to know that the only
2148921491
// promises that a host component might suspend on are definitely cached
2149021492
// because they are controlled by us. So don't bother.
21491-
resetHooksOnUnwind(); // Fallthrough to the next branch.
21493+
resetHooksOnUnwind(unitOfWork); // Fallthrough to the next branch.
2149221494
}
2149321495

2149421496
default: {
@@ -21534,7 +21536,7 @@ function throwAndUnwindWorkLoop(unitOfWork, thrownValue) {
2153421536
//
2153521537
// Return to the normal work loop. This will unwind the stack, and potentially
2153621538
// result in showing a fallback.
21537-
resetSuspendedWorkLoopOnUnwind();
21539+
resetSuspendedWorkLoopOnUnwind(unitOfWork);
2153821540
var returnFiber = unitOfWork.return;
2153921541

2154021542
if (returnFiber === null || workInProgressRoot === null) {
@@ -23892,7 +23894,7 @@ function createFiberRoot(
2389223894
return root;
2389323895
}
2389423896

23895-
var ReactVersion = "18.3.0-next-540bab085-20230426";
23897+
var ReactVersion = "18.3.0-next-18282f881-20230428";
2389623898

2389723899
// Might add PROFILE later.
2389823900

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

Lines changed: 31 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@
77
* @noflow
88
* @nolint
99
* @preventMunge
10-
* @generated SignedSource<<1d48b6135e400c479a77aa3a6d0aa557>>
10+
* @generated SignedSource<<36182b4efb97f53e0d4f613caa8813e2>>
1111
*/
1212

1313
"use strict";
@@ -2224,6 +2224,7 @@ function finishRenderingHooks() {
22242224
);
22252225
}
22262226
function renderWithHooksAgain(workInProgress, Component, props, secondArg) {
2227+
currentlyRenderingFiber$1 = workInProgress;
22272228
var numberOfReRenders = 0;
22282229
do {
22292230
didScheduleRenderPhaseUpdateDuringThisPass && (thenableState = null);
@@ -2246,12 +2247,16 @@ function bailoutHooks(current, workInProgress, lanes) {
22462247
workInProgress.flags &= -2053;
22472248
current.lanes &= ~lanes;
22482249
}
2249-
function resetHooksOnUnwind() {
2250+
function resetHooksOnUnwind(workInProgress) {
22502251
if (didScheduleRenderPhaseUpdate) {
2251-
for (var hook = currentlyRenderingFiber$1.memoizedState; null !== hook; ) {
2252-
var queue = hook.queue;
2252+
for (
2253+
workInProgress = workInProgress.memoizedState;
2254+
null !== workInProgress;
2255+
2256+
) {
2257+
var queue = workInProgress.queue;
22532258
null !== queue && (queue.pending = null);
2254-
hook = hook.next;
2259+
workInProgress = workInProgress.next;
22552260
}
22562261
didScheduleRenderPhaseUpdate = !1;
22572262
}
@@ -6681,8 +6686,9 @@ function resetWorkInProgressStack() {
66816686
if (0 === workInProgressSuspendedReason)
66826687
var interruptedWork = workInProgress.return;
66836688
else
6684-
resetContextDependencies(),
6685-
resetHooksOnUnwind(),
6689+
(interruptedWork = workInProgress),
6690+
resetContextDependencies(),
6691+
resetHooksOnUnwind(interruptedWork),
66866692
(thenableState$1 = null),
66876693
(thenableIndexCounter$1 = 0),
66886694
(interruptedWork = workInProgress);
@@ -6720,6 +6726,7 @@ function prepareFreshStack(root, lanes) {
67206726
return root;
67216727
}
67226728
function handleThrow(root, thrownValue) {
6729+
currentlyRenderingFiber$1 = null;
67236730
ReactCurrentDispatcher$1.current = ContextOnlyDispatcher;
67246731
ReactCurrentOwner.current = null;
67256732
thrownValue === SuspenseException
@@ -6974,7 +6981,7 @@ function replaySuspendedUnitOfWork(unitOfWork) {
69746981
);
69756982
break;
69766983
case 5:
6977-
resetHooksOnUnwind();
6984+
resetHooksOnUnwind(unitOfWork);
69786985
default:
69796986
unwindInterruptedWork(current, unitOfWork),
69806987
(unitOfWork = workInProgress =
@@ -6989,7 +6996,7 @@ function replaySuspendedUnitOfWork(unitOfWork) {
69896996
}
69906997
function throwAndUnwindWorkLoop(unitOfWork, thrownValue) {
69916998
resetContextDependencies();
6992-
resetHooksOnUnwind();
6999+
resetHooksOnUnwind(unitOfWork);
69937000
thenableState$1 = null;
69947001
thenableIndexCounter$1 = 0;
69957002
var returnFiber = unitOfWork.return;
@@ -8596,19 +8603,19 @@ function wrapFiber(fiber) {
85968603
fiberToWrapper.set(fiber, wrapper));
85978604
return wrapper;
85988605
}
8599-
var devToolsConfig$jscomp$inline_1022 = {
8606+
var devToolsConfig$jscomp$inline_1024 = {
86008607
findFiberByHostInstance: function () {
86018608
throw Error("TestRenderer does not support findFiberByHostInstance()");
86028609
},
86038610
bundleType: 0,
8604-
version: "18.3.0-next-540bab085-20230426",
8611+
version: "18.3.0-next-18282f881-20230428",
86058612
rendererPackageName: "react-test-renderer"
86068613
};
8607-
var internals$jscomp$inline_1207 = {
8608-
bundleType: devToolsConfig$jscomp$inline_1022.bundleType,
8609-
version: devToolsConfig$jscomp$inline_1022.version,
8610-
rendererPackageName: devToolsConfig$jscomp$inline_1022.rendererPackageName,
8611-
rendererConfig: devToolsConfig$jscomp$inline_1022.rendererConfig,
8614+
var internals$jscomp$inline_1209 = {
8615+
bundleType: devToolsConfig$jscomp$inline_1024.bundleType,
8616+
version: devToolsConfig$jscomp$inline_1024.version,
8617+
rendererPackageName: devToolsConfig$jscomp$inline_1024.rendererPackageName,
8618+
rendererConfig: devToolsConfig$jscomp$inline_1024.rendererConfig,
86128619
overrideHookState: null,
86138620
overrideHookStateDeletePath: null,
86148621
overrideHookStateRenamePath: null,
@@ -8625,26 +8632,26 @@ var internals$jscomp$inline_1207 = {
86258632
return null === fiber ? null : fiber.stateNode;
86268633
},
86278634
findFiberByHostInstance:
8628-
devToolsConfig$jscomp$inline_1022.findFiberByHostInstance ||
8635+
devToolsConfig$jscomp$inline_1024.findFiberByHostInstance ||
86298636
emptyFindFiberByHostInstance,
86308637
findHostInstancesForRefresh: null,
86318638
scheduleRefresh: null,
86328639
scheduleRoot: null,
86338640
setRefreshHandler: null,
86348641
getCurrentFiber: null,
8635-
reconcilerVersion: "18.3.0-next-540bab085-20230426"
8642+
reconcilerVersion: "18.3.0-next-18282f881-20230428"
86368643
};
86378644
if ("undefined" !== typeof __REACT_DEVTOOLS_GLOBAL_HOOK__) {
8638-
var hook$jscomp$inline_1208 = __REACT_DEVTOOLS_GLOBAL_HOOK__;
8645+
var hook$jscomp$inline_1210 = __REACT_DEVTOOLS_GLOBAL_HOOK__;
86398646
if (
8640-
!hook$jscomp$inline_1208.isDisabled &&
8641-
hook$jscomp$inline_1208.supportsFiber
8647+
!hook$jscomp$inline_1210.isDisabled &&
8648+
hook$jscomp$inline_1210.supportsFiber
86428649
)
86438650
try {
8644-
(rendererID = hook$jscomp$inline_1208.inject(
8645-
internals$jscomp$inline_1207
8651+
(rendererID = hook$jscomp$inline_1210.inject(
8652+
internals$jscomp$inline_1209
86468653
)),
8647-
(injectedHook = hook$jscomp$inline_1208);
8654+
(injectedHook = hook$jscomp$inline_1210);
86488655
} catch (err) {}
86498656
}
86508657
exports._Scheduler = Scheduler;

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

Lines changed: 31 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@
77
* @noflow
88
* @nolint
99
* @preventMunge
10-
* @generated SignedSource<<1f1b211c71736978a204aa9af530fa47>>
10+
* @generated SignedSource<<54c4cf0ba74fa6cfb2cf017a817e526a>>
1111
*/
1212

1313
"use strict";
@@ -2244,6 +2244,7 @@ function finishRenderingHooks() {
22442244
);
22452245
}
22462246
function renderWithHooksAgain(workInProgress, Component, props, secondArg) {
2247+
currentlyRenderingFiber$1 = workInProgress;
22472248
var numberOfReRenders = 0;
22482249
do {
22492250
didScheduleRenderPhaseUpdateDuringThisPass && (thenableState = null);
@@ -2266,12 +2267,16 @@ function bailoutHooks(current, workInProgress, lanes) {
22662267
workInProgress.flags &= -2053;
22672268
current.lanes &= ~lanes;
22682269
}
2269-
function resetHooksOnUnwind() {
2270+
function resetHooksOnUnwind(workInProgress) {
22702271
if (didScheduleRenderPhaseUpdate) {
2271-
for (var hook = currentlyRenderingFiber$1.memoizedState; null !== hook; ) {
2272-
var queue = hook.queue;
2272+
for (
2273+
workInProgress = workInProgress.memoizedState;
2274+
null !== workInProgress;
2275+
2276+
) {
2277+
var queue = workInProgress.queue;
22732278
null !== queue && (queue.pending = null);
2274-
hook = hook.next;
2279+
workInProgress = workInProgress.next;
22752280
}
22762281
didScheduleRenderPhaseUpdate = !1;
22772282
}
@@ -7021,8 +7026,9 @@ function resetWorkInProgressStack() {
70217026
if (0 === workInProgressSuspendedReason)
70227027
var interruptedWork = workInProgress.return;
70237028
else
7024-
resetContextDependencies(),
7025-
resetHooksOnUnwind(),
7029+
(interruptedWork = workInProgress),
7030+
resetContextDependencies(),
7031+
resetHooksOnUnwind(interruptedWork),
70267032
(thenableState$1 = null),
70277033
(thenableIndexCounter$1 = 0),
70287034
(interruptedWork = workInProgress);
@@ -7060,6 +7066,7 @@ function prepareFreshStack(root, lanes) {
70607066
return root;
70617067
}
70627068
function handleThrow(root, thrownValue) {
7069+
currentlyRenderingFiber$1 = null;
70637070
ReactCurrentDispatcher$1.current = ContextOnlyDispatcher;
70647071
ReactCurrentOwner.current = null;
70657072
thrownValue === SuspenseException
@@ -7325,7 +7332,7 @@ function replaySuspendedUnitOfWork(unitOfWork) {
73257332
);
73267333
break;
73277334
case 5:
7328-
resetHooksOnUnwind();
7335+
resetHooksOnUnwind(unitOfWork);
73297336
default:
73307337
unwindInterruptedWork(current, unitOfWork),
73317338
(unitOfWork = workInProgress =
@@ -7341,7 +7348,7 @@ function replaySuspendedUnitOfWork(unitOfWork) {
73417348
}
73427349
function throwAndUnwindWorkLoop(unitOfWork, thrownValue) {
73437350
resetContextDependencies();
7344-
resetHooksOnUnwind();
7351+
resetHooksOnUnwind(unitOfWork);
73457352
thenableState$1 = null;
73467353
thenableIndexCounter$1 = 0;
73477354
var returnFiber = unitOfWork.return;
@@ -9022,19 +9029,19 @@ function wrapFiber(fiber) {
90229029
fiberToWrapper.set(fiber, wrapper));
90239030
return wrapper;
90249031
}
9025-
var devToolsConfig$jscomp$inline_1064 = {
9032+
var devToolsConfig$jscomp$inline_1066 = {
90269033
findFiberByHostInstance: function () {
90279034
throw Error("TestRenderer does not support findFiberByHostInstance()");
90289035
},
90299036
bundleType: 0,
9030-
version: "18.3.0-next-540bab085-20230426",
9037+
version: "18.3.0-next-18282f881-20230428",
90319038
rendererPackageName: "react-test-renderer"
90329039
};
9033-
var internals$jscomp$inline_1248 = {
9034-
bundleType: devToolsConfig$jscomp$inline_1064.bundleType,
9035-
version: devToolsConfig$jscomp$inline_1064.version,
9036-
rendererPackageName: devToolsConfig$jscomp$inline_1064.rendererPackageName,
9037-
rendererConfig: devToolsConfig$jscomp$inline_1064.rendererConfig,
9040+
var internals$jscomp$inline_1250 = {
9041+
bundleType: devToolsConfig$jscomp$inline_1066.bundleType,
9042+
version: devToolsConfig$jscomp$inline_1066.version,
9043+
rendererPackageName: devToolsConfig$jscomp$inline_1066.rendererPackageName,
9044+
rendererConfig: devToolsConfig$jscomp$inline_1066.rendererConfig,
90389045
overrideHookState: null,
90399046
overrideHookStateDeletePath: null,
90409047
overrideHookStateRenamePath: null,
@@ -9051,26 +9058,26 @@ var internals$jscomp$inline_1248 = {
90519058
return null === fiber ? null : fiber.stateNode;
90529059
},
90539060
findFiberByHostInstance:
9054-
devToolsConfig$jscomp$inline_1064.findFiberByHostInstance ||
9061+
devToolsConfig$jscomp$inline_1066.findFiberByHostInstance ||
90559062
emptyFindFiberByHostInstance,
90569063
findHostInstancesForRefresh: null,
90579064
scheduleRefresh: null,
90589065
scheduleRoot: null,
90599066
setRefreshHandler: null,
90609067
getCurrentFiber: null,
9061-
reconcilerVersion: "18.3.0-next-540bab085-20230426"
9068+
reconcilerVersion: "18.3.0-next-18282f881-20230428"
90629069
};
90639070
if ("undefined" !== typeof __REACT_DEVTOOLS_GLOBAL_HOOK__) {
9064-
var hook$jscomp$inline_1249 = __REACT_DEVTOOLS_GLOBAL_HOOK__;
9071+
var hook$jscomp$inline_1251 = __REACT_DEVTOOLS_GLOBAL_HOOK__;
90659072
if (
9066-
!hook$jscomp$inline_1249.isDisabled &&
9067-
hook$jscomp$inline_1249.supportsFiber
9073+
!hook$jscomp$inline_1251.isDisabled &&
9074+
hook$jscomp$inline_1251.supportsFiber
90689075
)
90699076
try {
9070-
(rendererID = hook$jscomp$inline_1249.inject(
9071-
internals$jscomp$inline_1248
9077+
(rendererID = hook$jscomp$inline_1251.inject(
9078+
internals$jscomp$inline_1250
90729079
)),
9073-
(injectedHook = hook$jscomp$inline_1249);
9080+
(injectedHook = hook$jscomp$inline_1251);
90749081
} catch (err) {}
90759082
}
90769083
exports._Scheduler = Scheduler;

compiled-rn/facebook-fbsource/xplat/js/RKJSModules/vendor/react/cjs/React-dev.js

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -27,7 +27,7 @@ if (
2727
}
2828
"use strict";
2929

30-
var ReactVersion = "18.3.0-next-540bab085-20230426";
30+
var ReactVersion = "18.3.0-next-18282f881-20230428";
3131

3232
// ATTENTION
3333
// When adding new symbols to this file,

compiled-rn/facebook-fbsource/xplat/js/RKJSModules/vendor/react/cjs/React-prod.js

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -639,4 +639,4 @@ exports.useSyncExternalStore = function (
639639
);
640640
};
641641
exports.useTransition = useTransition;
642-
exports.version = "18.3.0-next-540bab085-20230426";
642+
exports.version = "18.3.0-next-18282f881-20230428";

compiled-rn/facebook-fbsource/xplat/js/RKJSModules/vendor/react/cjs/React-profiling.js

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -642,7 +642,7 @@ exports.useSyncExternalStore = function (
642642
);
643643
};
644644
exports.useTransition = useTransition;
645-
exports.version = "18.3.0-next-540bab085-20230426";
645+
exports.version = "18.3.0-next-18282f881-20230428";
646646

647647
/* global __REACT_DEVTOOLS_GLOBAL_HOOK__ */
648648
if (
Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1 +1 @@
1-
540bab085d571789f4562565eebfd0db9f36345c
1+
18282f881dae106ebf6240aa52c8c02fe7c8d6f2

0 commit comments

Comments
 (0)