Skip to content

Commit

Permalink
[skip ci] More spec-compliant execution of microtasks
Browse files Browse the repository at this point in the history
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
  • Loading branch information
rubennorte authored and facebook-github-bot committed Feb 28, 2024
1 parent 7cfd686 commit 04bd850
Show file tree
Hide file tree
Showing 3 changed files with 92 additions and 35 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -11,45 +11,12 @@
#include <cxxreact/ErrorUtils.h>
#include <react/featureflags/ReactNativeFeatureFlags.h>
#include <react/renderer/debug/SystraceSection.h>
#include <react/utils/SetForScope.h>
#include <utility>
#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(
Expand Down Expand Up @@ -298,7 +265,7 @@ void RuntimeScheduler_Modern::executeTask(

if (ReactNativeFeatureFlags::enableMicrotasks()) {
// "Perform a microtask checkpoint" step.
executeMicrotasks(runtime);
performMicrotaskCheckpoint(runtime);
}

if (ReactNativeFeatureFlags::batchRenderingUpdatesInEventLoop()) {
Expand Down Expand Up @@ -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
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Expand Down
48 changes: 48 additions & 0 deletions packages/react-native/ReactCommon/react/utils/SetForScope.h
Original file line number Diff line number Diff line change
@@ -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 <memory>

namespace facebook::react {

template <typename T>
class SetForScope {
public:
explicit SetForScope(T& variable)
: variable_(variable), valueToRestore_(variable) {}

template <typename U>
SetForScope(T& variable, U&& newValue) : SetForScope(variable) {
variable_ = std::forward<U>(newValue);
}

template <typename U, typename V>
SetForScope(T& variable, U&& newValue, V&& valueToRestore)
: variable_(variable), valueToRestore_(std::forward<V>(valueToRestore)) {
variable_ = std::forward<U>(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

0 comments on commit 04bd850

Please sign in to comment.