From 04bd8507a1e052c90aa30e2bbfb936d274661716 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Rub=C3=A9n=20Norte?= Date: Wed, 28 Feb 2024 10:10:11 -0800 Subject: [PATCH] [skip ci] More spec-compliant execution of microtasks Summary: Changelog: [internal] This modifies the method to run microtasks in `RuntimeScheduler_Modern` to align a bit better with the spec. In this case, we'll check if we're already running microtasks when we call that method, and skip if that's the case. We're not currently calling this method recursively so this shouldn't really be a change with the current logic. Differential Revision: D54302537 --- .../RuntimeScheduler_Modern.cpp | 76 ++++++++++--------- .../RuntimeScheduler_Modern.h | 3 + .../ReactCommon/react/utils/SetForScope.h | 48 ++++++++++++ 3 files changed, 92 insertions(+), 35 deletions(-) create mode 100644 packages/react-native/ReactCommon/react/utils/SetForScope.h diff --git a/packages/react-native/ReactCommon/react/renderer/runtimescheduler/RuntimeScheduler_Modern.cpp b/packages/react-native/ReactCommon/react/renderer/runtimescheduler/RuntimeScheduler_Modern.cpp index 4a30a2ff5c4034..0e6fb5bae0a901 100644 --- a/packages/react-native/ReactCommon/react/renderer/runtimescheduler/RuntimeScheduler_Modern.cpp +++ b/packages/react-native/ReactCommon/react/renderer/runtimescheduler/RuntimeScheduler_Modern.cpp @@ -11,45 +11,12 @@ #include #include #include +#include #include #include "ErrorUtils.h" namespace facebook::react { -namespace { -/** - * This is partially equivalent to the "Perform a microtask checkpoint" step in - * the Web event loop. See - * https://html.spec.whatwg.org/multipage/webappapis.html#perform-a-microtask-checkpoint. - * - * Iterates on \c drainMicrotasks until it completes or hits the retries bound. - */ -void executeMicrotasks(jsi::Runtime& runtime) { - SystraceSection s("RuntimeScheduler::executeMicrotasks"); - - uint8_t retries = 0; - // A heuristic number to guard infinite or absurd numbers of retries. - const static unsigned int kRetriesBound = 255; - - while (retries < kRetriesBound) { - try { - // The default behavior of \c drainMicrotasks is unbounded execution. - // We may want to make it bounded in the future. - if (runtime.drainMicrotasks()) { - break; - } - } catch (jsi::JSError& error) { - handleJSError(runtime, error, true); - } - retries++; - } - - if (retries == kRetriesBound) { - throw std::runtime_error("Hits microtasks retries bound."); - } -} -} // namespace - #pragma mark - Public RuntimeScheduler_Modern::RuntimeScheduler_Modern( @@ -298,7 +265,7 @@ void RuntimeScheduler_Modern::executeTask( if (ReactNativeFeatureFlags::enableMicrotasks()) { // "Perform a microtask checkpoint" step. - executeMicrotasks(runtime); + performMicrotaskCheckpoint(runtime); } if (ReactNativeFeatureFlags::batchRenderingUpdatesInEventLoop()) { @@ -339,4 +306,43 @@ void RuntimeScheduler_Modern::executeMacrotask( } } +/** + * This is partially equivalent to the "Perform a microtask checkpoint" step in + * the Web event loop. See + * https://html.spec.whatwg.org/multipage/webappapis.html#perform-a-microtask-checkpoint. + * + * Iterates on \c drainMicrotasks until it completes or hits the retries bound. + */ +void RuntimeScheduler_Modern::performMicrotaskCheckpoint( + jsi::Runtime& runtime) { + SystraceSection s("RuntimeScheduler::performMicrotaskCheckpoint"); + + if (performingMicrotaskCheckpoint_) { + return; + } + + SetForScope change(performingMicrotaskCheckpoint_, true); + + uint8_t retries = 0; + // A heuristic number to guard infinite or absurd numbers of retries. + const static unsigned int kRetriesBound = 255; + + while (retries < kRetriesBound) { + try { + // The default behavior of \c drainMicrotasks is unbounded execution. + // We may want to make it bounded in the future. + if (runtime.drainMicrotasks()) { + break; + } + } catch (jsi::JSError& error) { + handleJSError(runtime, error, true); + } + retries++; + } + + if (retries == kRetriesBound) { + throw std::runtime_error("Hits microtasks retries bound."); + } +} + } // namespace facebook::react diff --git a/packages/react-native/ReactCommon/react/renderer/runtimescheduler/RuntimeScheduler_Modern.h b/packages/react-native/ReactCommon/react/renderer/runtimescheduler/RuntimeScheduler_Modern.h index 34c8eb4cf16cbf..0759d8470bd54b 100644 --- a/packages/react-native/ReactCommon/react/renderer/runtimescheduler/RuntimeScheduler_Modern.h +++ b/packages/react-native/ReactCommon/react/renderer/runtimescheduler/RuntimeScheduler_Modern.h @@ -178,6 +178,9 @@ class RuntimeScheduler_Modern final : public RuntimeSchedulerBase { void updateRendering(); + bool performingMicrotaskCheckpoint_{false}; + void performMicrotaskCheckpoint(jsi::Runtime& runtime); + /* * Returns a time point representing the current point in time. May be called * from multiple threads. diff --git a/packages/react-native/ReactCommon/react/utils/SetForScope.h b/packages/react-native/ReactCommon/react/utils/SetForScope.h new file mode 100644 index 00000000000000..aaeca9055fa8e7 --- /dev/null +++ b/packages/react-native/ReactCommon/react/utils/SetForScope.h @@ -0,0 +1,48 @@ +/* + * Copyright (c) Meta Platforms, Inc. and affiliates. + * + * This source code is licensed under the MIT license found in the + * LICENSE file in the root directory of this source tree. + */ + +#pragma once + +#include + +namespace facebook::react { + +template +class SetForScope { + public: + explicit SetForScope(T& variable) + : variable_(variable), valueToRestore_(variable) {} + + template + SetForScope(T& variable, U&& newValue) : SetForScope(variable) { + variable_ = std::forward(newValue); + } + + template + SetForScope(T& variable, U&& newValue, V&& valueToRestore) + : variable_(variable), valueToRestore_(std::forward(valueToRestore)) { + variable_ = std::forward(newValue); + } + + // Non-movable + SetForScope(const SetForScope&) = delete; + SetForScope(SetForScope&&) = delete; + + // Non-copyable + SetForScope& operator=(const SetForScope&) = delete; + SetForScope& operator=(SetForScope&&) = delete; + + ~SetForScope() { + variable_ = std::move(valueToRestore_); + } + + private: + T& variable_; + T valueToRestore_; +}; + +} // namespace facebook::react