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.

Reviewed By: javache

Differential Revision: D54302537

fbshipit-source-id: ef5e12e68e0c7f8c9258929609c050ef78e4cde5
This commit is contained in:
Rubén Norte
2024-02-29 11:38:30 -08:00
committed by Facebook GitHub Bot
parent 4d982dcdae
commit 21171222eb
3 changed files with 81 additions and 35 deletions
@@ -11,45 +11,12 @@
#include <cxxreact/ErrorUtils.h>
#include <react/featureflags/ReactNativeFeatureFlags.h>
#include <react/renderer/debug/SystraceSection.h>
#include <react/utils/OnScopeExit.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(
@@ -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,44 @@ 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;
}
performingMicrotaskCheckpoint_ = true;
OnScopeExit restoreFlag([&]() { performingMicrotaskCheckpoint_ = false; });
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
@@ -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.
@@ -0,0 +1,36 @@
/*
* 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 <functional>
namespace facebook::react {
template <typename Lambda>
class OnScopeExit {
public:
explicit OnScopeExit(const Lambda&& lambda) noexcept
: lambda_(std::move(lambda)) {}
// Non-movable
OnScopeExit(const OnScopeExit&) = delete;
OnScopeExit(OnScopeExit&&) = delete;
// Non-copyable
OnScopeExit& operator=(const OnScopeExit&) = delete;
OnScopeExit& operator=(OnScopeExit&&) = delete;
~OnScopeExit() noexcept {
lambda_();
}
private:
Lambda lambda_;
};
} // namespace facebook::react