Skip to content

Commit 14184ec

Browse files
javachemeta-codesync[bot]
authored andcommitted
Enable RuntimeScheduler queue clearing on error by default (#58129)
Summary: Pull Request resolved: #58129 The experiment for clearing the `RuntimeScheduler` queues before handling a task error has run its course, so make the behaviour the default for everyone. When a task throws, `RuntimeScheduler_Modern` now drops the pending task queue and any pending rendering updates before invoking the host error handler. That stops queued work from being executed against state the error handler is about to tear down. Turning this on for everyone exposed a bug in one of the call sites. A throwing resize-observer callback was routed through `handleTaskError`, which clears the queues - wrong inside the "update the rendering" step, because it drops the pending rendering updates that same step is about to drain, so a throwing observer callback aborts the remaining steps. That is exactly what the call site's own comment says must not happen. That path now reports the error without clearing. Queue clearing stays on the task-execution and microtask-checkpoint paths, which are the ones that unwind before the rest of the tick runs. The flag default flips to `true`, and the per-app runtime gates are removed so every consumer picks up the default rather than an override. Changelog: [General][Changed] - `RuntimeScheduler` now clears pending tasks and rendering updates when a task throws Reviewed By: christophpurrer Differential Revision: D117322680 fbshipit-source-id: e47de49b0fbb240023f8ee25bdb111a19b50a5b1
1 parent 39cd1df commit 14184ec

8 files changed

Lines changed: 22 additions & 20 deletions

File tree

packages/react-native/ReactAndroid/src/main/java/com/facebook/react/internal/featureflags/ReactNativeFeatureFlagsDefaults.kt

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@
44
* This source code is licensed under the MIT license found in the
55
* LICENSE file in the root directory of this source tree.
66
*
7-
* @generated SignedSource<<74896623764d3d01d2925fdcf30948f1>>
7+
* @generated SignedSource<<c7480fceb75a5f614abf28813c501a54>>
88
*/
99

1010
/**
@@ -109,7 +109,7 @@ public open class ReactNativeFeatureFlagsDefaults : ReactNativeFeatureFlagsProvi
109109

110110
override fun enableResizeObserverByDefault(): Boolean = false
111111

112-
override fun enableRuntimeSchedulerQueueClearingOnError(): Boolean = false
112+
override fun enableRuntimeSchedulerQueueClearingOnError(): Boolean = true
113113

114114
override fun enableSchedulerDelegateInvalidation(): Boolean = false
115115

packages/react-native/ReactAndroid/src/main/java/com/facebook/react/internal/featureflags/ReactNativeFeatureFlagsOverrides_RNOSS_Experimental_Android.kt

Lines changed: 1 addition & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@
44
* This source code is licensed under the MIT license found in the
55
* LICENSE file in the root directory of this source tree.
66
*
7-
* @generated SignedSource<<7103d9c2fa3e2f152ce60b5dff12c0e4>>
7+
* @generated SignedSource<<29904c9a4e11c71f7657c49f3f45a32f>>
88
*/
99

1010
/**
@@ -25,8 +25,6 @@ public open class ReactNativeFeatureFlagsOverrides_RNOSS_Experimental_Android :
2525

2626
override fun enableFlexboxAutoMinSizeInStrictMode(): Boolean = true
2727

28-
override fun enableRuntimeSchedulerQueueClearingOnError(): Boolean = true
29-
3028
override fun enableSchedulerDelegateInvalidation(): Boolean = true
3129

3230
override fun preventShadowTreeCommitExhaustion(): Boolean = true

packages/react-native/ReactCommon/react/featureflags/ReactNativeFeatureFlagsDefaults.h

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@
44
* This source code is licensed under the MIT license found in the
55
* LICENSE file in the root directory of this source tree.
66
*
7-
* @generated SignedSource<<0dcc09eb60ab85de9bc72410e93c2a1c>>
7+
* @generated SignedSource<<374da5049c71e475cb3e5c2bfed87c9c>>
88
*/
99

1010
/**
@@ -200,7 +200,7 @@ class ReactNativeFeatureFlagsDefaults : public ReactNativeFeatureFlagsProvider {
200200
}
201201

202202
bool enableRuntimeSchedulerQueueClearingOnError() override {
203-
return false;
203+
return true;
204204
}
205205

206206
bool enableSchedulerDelegateInvalidation() override {

packages/react-native/ReactCommon/react/featureflags/ReactNativeFeatureFlagsOverridesOSSExperimental.h

Lines changed: 1 addition & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@
44
* This source code is licensed under the MIT license found in the
55
* LICENSE file in the root directory of this source tree.
66
*
7-
* @generated SignedSource<<7341b28bb77aeeec670051149faeae04>>
7+
* @generated SignedSource<<764568389a1ae8675638104ab3d96d4d>>
88
*/
99

1010
/**
@@ -31,10 +31,6 @@ class ReactNativeFeatureFlagsOverridesOSSExperimental : public ReactNativeFeatur
3131
return true;
3232
}
3333

34-
bool enableRuntimeSchedulerQueueClearingOnError() override {
35-
return true;
36-
}
37-
3834
bool enableSchedulerDelegateInvalidation() override {
3935
return true;
4036
}

packages/react-native/ReactCommon/react/renderer/runtimescheduler/RuntimeScheduler_Modern.cpp

Lines changed: 7 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -370,16 +370,19 @@ void RuntimeScheduler_Modern::updateRendering(
370370
// internally; this call site stays a single invocation per tick.
371371
if (resizeObserverDelegate_ != nullptr) {
372372
// This delivers the observations to JS synchronously, so it is outside the
373-
// error boundary of `executeTask`. Handle errors the same way here, so a
374-
// throwing observer callback can't abort the remaining steps below.
373+
// error boundary of `executeTask`. Report the error without going through
374+
// `handleTaskError`: we are already inside the "update the rendering" step,
375+
// so clearing the queues here would drop the pending rendering updates this
376+
// step is about to drain, and a throwing observer callback would abort the
377+
// remaining steps below.
375378
try {
376379
resizeObserverDelegate_->runResizeObservations(runtime);
377380
} catch (jsi::JSError& error) {
378-
handleTaskError(runtime, error);
381+
onTaskError_(runtime, error);
379382
} catch (std::exception& ex) {
380383
jsi::JSError error(
381384
runtime, std::string("Non-JS exception: ") + ex.what());
382-
handleTaskError(runtime, error);
385+
onTaskError_(runtime, error);
383386
}
384387
}
385388

packages/react-native/ReactCommon/react/renderer/runtimescheduler/tests/RuntimeSchedulerTest.cpp

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1826,6 +1826,8 @@ TEST_P(RuntimeSchedulerTest, errorInResizeObservationsDoesNotStopUpdate) {
18261826
return;
18271827
}
18281828

1829+
setUpFeatureFlags(/*enableRuntimeSchedulerQueueClearingOnError=*/true);
1830+
18291831
StubResizeObserverDelegate resizeObserverDelegate;
18301832
resizeObserverDelegate.onRunResizeObservations = [](jsi::Runtime& runtime) {
18311833
throw jsi::JSError(runtime, "Test error");
@@ -1848,6 +1850,9 @@ TEST_P(RuntimeSchedulerTest, errorInResizeObservationsDoesNotStopUpdate) {
18481850

18491851
// Delivering observations to JS synchronously happens outside the error
18501852
// boundary of the task, so the step handles the error itself and carries on.
1853+
// In particular the error must not be routed through `handleTaskError`:
1854+
// that clears the pending rendering updates this same step is about to
1855+
// drain, and `didRunRenderingUpdate` below would be false.
18511856
EXPECT_EQ(stubErrorUtils_->getReportFatalCallCount(), 1);
18521857
EXPECT_EQ(resizeObserverDelegate.callCount, 1);
18531858
EXPECT_EQ(intersectionObserverDelegate.callCount, 1);

packages/react-native/scripts/featureflags/ReactNativeFeatureFlags.config.js

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -506,15 +506,15 @@ const definitions: FeatureFlagDefinitions = {
506506
ossReleaseStage: 'none',
507507
},
508508
enableRuntimeSchedulerQueueClearingOnError: {
509-
defaultValue: false,
509+
defaultValue: true,
510510
metadata: {
511511
dateAdded: '2026-05-19',
512512
description:
513513
'When enabled, RuntimeScheduler_Modern clears pending tasks and rendering updates before handling an error.',
514514
expectedReleaseValue: true,
515515
purpose: 'experimentation',
516516
},
517-
ossReleaseStage: 'experimental',
517+
ossReleaseStage: 'none',
518518
},
519519
enableSchedulerDelegateInvalidation: {
520520
defaultValue: false,

packages/react-native/src/private/featureflags/ReactNativeFeatureFlags.js

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@
44
* This source code is licensed under the MIT license found in the
55
* LICENSE file in the root directory of this source tree.
66
*
7-
* @generated SignedSource<<fbab08ee89a2969b45034a45b6425581>>
7+
* @generated SignedSource<<2adcae106ae1e758061a91ee20f902fd>>
88
* @flow strict
99
* @noformat
1010
*/
@@ -387,7 +387,7 @@ export const enableResizeObserverByDefault: Getter<boolean> = createNativeFlagGe
387387
/**
388388
* When enabled, RuntimeScheduler_Modern clears pending tasks and rendering updates before handling an error.
389389
*/
390-
export const enableRuntimeSchedulerQueueClearingOnError: Getter<boolean> = createNativeFlagGetter('enableRuntimeSchedulerQueueClearingOnError', false);
390+
export const enableRuntimeSchedulerQueueClearingOnError: Getter<boolean> = createNativeFlagGetter('enableRuntimeSchedulerQueueClearingOnError', true);
391391
/**
392392
* Gates a defensive guard around Scheduler::uiManagerDidDispatchCommand and uiManagerDidFinishTransaction that prevents queued rendering-update lambdas from dereferencing the SchedulerDelegate after it has been destroyed (use-after-free).
393393
*/

0 commit comments

Comments
 (0)