Create proxy for RuntimeScheduler to allow us to use a forked version (#40875)

Summary:
Pull Request resolved: https://github.com/facebook/react-native/pull/40875

This introduces a proxy for RuntimeScheduler so we can select between 2 different implementations at runtime (current implementation vs. new implementation, done in D49316881).

Changelog: [internal]

Reviewed By: javache, sammy-SC

Differential Revision: D49316880

fbshipit-source-id: 4035ed6ba641a2316f2efb7cf4a0a86270d6ae23
This commit is contained in:
Rubén Norte 2023-10-18 06:14:25 -07:00 коммит произвёл Facebook GitHub Bot
Родитель 29bbab5a5a
Коммит c0e2c68cdc
5 изменённых файлов: 408 добавлений и 196 удалений

Просмотреть файл

@ -6,6 +6,7 @@
*/
#include "RuntimeScheduler.h"
#include "RuntimeScheduler_Legacy.h"
#include "SchedulerPriorityUtils.h"
#include <react/renderer/debug/SystraceSection.h>
@ -14,177 +15,55 @@
namespace facebook::react {
#pragma mark - Public
RuntimeScheduler::RuntimeScheduler(
RuntimeExecutor runtimeExecutor,
std::function<RuntimeSchedulerTimePoint()> now)
: runtimeExecutor_(std::move(runtimeExecutor)), now_(std::move(now)) {}
: runtimeSchedulerImpl_(std::make_unique<RuntimeScheduler_Legacy>(
std::move(runtimeExecutor),
std::move(now))) {}
void RuntimeScheduler::scheduleWork(RawCallback&& callback) const noexcept {
SystraceSection s("RuntimeScheduler::scheduleWork");
runtimeAccessRequests_ += 1;
runtimeExecutor_(
[this, callback = std::move(callback)](jsi::Runtime& runtime) {
SystraceSection s2("RuntimeScheduler::scheduleWork callback");
runtimeAccessRequests_ -= 1;
callback(runtime);
startWorkLoop(runtime);
});
return runtimeSchedulerImpl_->scheduleWork(std::move(callback));
}
std::shared_ptr<Task> RuntimeScheduler::scheduleTask(
SchedulerPriority priority,
jsi::Function&& callback) noexcept {
auto expirationTime = now_() + timeoutForSchedulerPriority(priority);
auto task =
std::make_shared<Task>(priority, std::move(callback), expirationTime);
taskQueue_.push(task);
scheduleWorkLoopIfNecessary();
return task;
return runtimeSchedulerImpl_->scheduleTask(priority, std::move(callback));
}
std::shared_ptr<Task> RuntimeScheduler::scheduleTask(
SchedulerPriority priority,
RawCallback&& callback) noexcept {
auto expirationTime = now_() + timeoutForSchedulerPriority(priority);
auto task =
std::make_shared<Task>(priority, std::move(callback), expirationTime);
taskQueue_.push(task);
scheduleWorkLoopIfNecessary();
return task;
return runtimeSchedulerImpl_->scheduleTask(priority, std::move(callback));
}
bool RuntimeScheduler::getShouldYield() const noexcept {
return runtimeAccessRequests_ > 0;
return runtimeSchedulerImpl_->getShouldYield();
}
bool RuntimeScheduler::getIsSynchronous() const noexcept {
return isSynchronous_;
return runtimeSchedulerImpl_->getIsSynchronous();
}
void RuntimeScheduler::cancelTask(Task& task) noexcept {
task.callback.reset();
return runtimeSchedulerImpl_->cancelTask(task);
}
SchedulerPriority RuntimeScheduler::getCurrentPriorityLevel() const noexcept {
return currentPriority_;
return runtimeSchedulerImpl_->getCurrentPriorityLevel();
}
RuntimeSchedulerTimePoint RuntimeScheduler::now() const noexcept {
return now_();
return runtimeSchedulerImpl_->now();
}
void RuntimeScheduler::executeNowOnTheSameThread(RawCallback&& callback) {
SystraceSection s("RuntimeScheduler::executeNowOnTheSameThread");
runtimeAccessRequests_ += 1;
executeSynchronouslyOnSameThread_CAN_DEADLOCK(
runtimeExecutor_,
[this, callback = std::move(callback)](jsi::Runtime& runtime) {
SystraceSection s2(
"RuntimeScheduler::executeNowOnTheSameThread callback");
runtimeAccessRequests_ -= 1;
isSynchronous_ = true;
callback(runtime);
isSynchronous_ = false;
});
// Resume work loop if needed. In synchronous mode
// only expired tasks are executed. Tasks with lower priority
// might be still in the queue.
scheduleWorkLoopIfNecessary();
return runtimeSchedulerImpl_->executeNowOnTheSameThread(std::move(callback));
}
void RuntimeScheduler::callExpiredTasks(jsi::Runtime& runtime) {
SystraceSection s("RuntimeScheduler::callExpiredTasks");
auto previousPriority = currentPriority_;
try {
while (!taskQueue_.empty()) {
auto topPriorityTask = taskQueue_.top();
auto now = now_();
auto didUserCallbackTimeout = topPriorityTask->expirationTime <= now;
if (!didUserCallbackTimeout) {
break;
}
executeTask(runtime, topPriorityTask, didUserCallbackTimeout);
}
} catch (jsi::JSError& error) {
handleFatalError(runtime, error);
}
currentPriority_ = previousPriority;
}
#pragma mark - Private
void RuntimeScheduler::scheduleWorkLoopIfNecessary() const {
if (!isWorkLoopScheduled_ && !isPerformingWork_) {
isWorkLoopScheduled_ = true;
runtimeExecutor_([this](jsi::Runtime& runtime) {
isWorkLoopScheduled_ = false;
startWorkLoop(runtime);
});
}
}
void RuntimeScheduler::startWorkLoop(jsi::Runtime& runtime) const {
SystraceSection s("RuntimeScheduler::startWorkLoop");
auto previousPriority = currentPriority_;
isPerformingWork_ = true;
try {
while (!taskQueue_.empty()) {
auto topPriorityTask = taskQueue_.top();
auto now = now_();
auto didUserCallbackTimeout = topPriorityTask->expirationTime <= now;
if (!didUserCallbackTimeout && getShouldYield()) {
// This currentTask hasn't expired, and we need to yield.
break;
}
executeTask(runtime, topPriorityTask, didUserCallbackTimeout);
}
} catch (jsi::JSError& error) {
handleFatalError(runtime, error);
}
currentPriority_ = previousPriority;
isPerformingWork_ = false;
}
void RuntimeScheduler::executeTask(
jsi::Runtime& runtime,
const std::shared_ptr<Task>& task,
bool didUserCallbackTimeout) const {
SystraceSection s(
"RuntimeScheduler::executeTask",
"priority",
serialize(task->priority),
"didUserCallbackTimeout",
didUserCallbackTimeout);
currentPriority_ = task->priority;
auto result = task->execute(runtime, didUserCallbackTimeout);
if (result.isObject() && result.getObject(runtime).isFunction(runtime)) {
task->callback = result.getObject(runtime).getFunction(runtime);
} else {
if (taskQueue_.top() == task) {
taskQueue_.pop();
}
}
return runtimeSchedulerImpl_->callExpiredTasks(runtime);
}
} // namespace facebook::react

Просмотреть файл

@ -10,18 +10,39 @@
#include <ReactCommon/RuntimeExecutor.h>
#include <react/renderer/runtimescheduler/RuntimeSchedulerClock.h>
#include <react/renderer/runtimescheduler/Task.h>
#include <atomic>
#include <memory>
#include <queue>
namespace facebook::react {
class RuntimeScheduler final {
// This is a temporary abstract class for RuntimeScheduler forks to implement
// (and use them interchangeably).
class RuntimeSchedulerBase {
public:
virtual ~RuntimeSchedulerBase() = default;
virtual void scheduleWork(RawCallback&& callback) const noexcept = 0;
virtual void executeNowOnTheSameThread(RawCallback&& callback) = 0;
virtual std::shared_ptr<Task> scheduleTask(
SchedulerPriority priority,
jsi::Function&& callback) noexcept = 0;
virtual std::shared_ptr<Task> scheduleTask(
SchedulerPriority priority,
RawCallback&& callback) noexcept = 0;
virtual void cancelTask(Task& task) noexcept = 0;
virtual bool getShouldYield() const noexcept = 0;
virtual bool getIsSynchronous() const noexcept = 0;
virtual SchedulerPriority getCurrentPriorityLevel() const noexcept = 0;
virtual RuntimeSchedulerTimePoint now() const noexcept = 0;
virtual void callExpiredTasks(jsi::Runtime& runtime) = 0;
};
// This is a proxy for RuntimeScheduler implementation, which will be selected
// at runtime based on a feature flag.
class RuntimeScheduler final : RuntimeSchedulerBase {
public:
RuntimeScheduler(
RuntimeExecutor runtimeExecutor,
std::function<RuntimeSchedulerTimePoint()> now =
RuntimeSchedulerClock::now);
/*
* Not copyable.
*/
@ -34,7 +55,7 @@ class RuntimeScheduler final {
RuntimeScheduler(RuntimeScheduler&&) = delete;
RuntimeScheduler& operator=(RuntimeScheduler&&) = delete;
void scheduleWork(RawCallback&& callback) const noexcept;
void scheduleWork(RawCallback&& callback) const noexcept override;
/*
* Grants access to the runtime synchronously on the caller's thread.
@ -43,7 +64,7 @@ class RuntimeScheduler final {
* by dispatching a synchronous event via event emitter in your native
* component.
*/
void executeNowOnTheSameThread(RawCallback&& callback);
void executeNowOnTheSameThread(RawCallback&& callback) override;
/*
* Adds a JavaScript callback to priority queue with given priority.
@ -53,11 +74,11 @@ class RuntimeScheduler final {
*/
std::shared_ptr<Task> scheduleTask(
SchedulerPriority priority,
jsi::Function&& callback) noexcept;
jsi::Function&& callback) noexcept override;
std::shared_ptr<Task> scheduleTask(
SchedulerPriority priority,
RawCallback&& callback) noexcept;
RawCallback&& callback) noexcept override;
/*
* Cancelled task will never be executed.
@ -65,7 +86,7 @@ class RuntimeScheduler final {
* Operates on JSI object.
* Thread synchronization must be enforced externally.
*/
void cancelTask(Task& task) noexcept;
void cancelTask(Task& task) noexcept override;
/*
* Return value indicates if host platform has a pending access to the
@ -73,7 +94,7 @@ class RuntimeScheduler final {
*
* Can be called from any thread.
*/
bool getShouldYield() const noexcept;
bool getShouldYield() const noexcept override;
/*
* Return value informs if the current task is executed inside synchronous
@ -81,14 +102,14 @@ class RuntimeScheduler final {
*
* Can be called from any thread.
*/
bool getIsSynchronous() const noexcept;
bool getIsSynchronous() const noexcept override;
/*
* Returns value of currently executed task. Designed to be called from React.
*
* Thread synchronization must be enforced externally.
*/
SchedulerPriority getCurrentPriorityLevel() const noexcept;
SchedulerPriority getCurrentPriorityLevel() const noexcept override;
/*
* Returns current monotonic time. This time is not related to wall clock
@ -96,7 +117,7 @@ class RuntimeScheduler final {
*
* Thread synchronization must be enforced externally.
*/
RuntimeSchedulerTimePoint now() const noexcept;
RuntimeSchedulerTimePoint now() const noexcept override;
/*
* Expired task is a task that should have been already executed. Designed to
@ -106,54 +127,12 @@ class RuntimeScheduler final {
*
* Thread synchronization must be enforced externally.
*/
void callExpiredTasks(jsi::Runtime& runtime);
void callExpiredTasks(jsi::Runtime& runtime) override;
private:
mutable std::priority_queue<
std::shared_ptr<Task>,
std::vector<std::shared_ptr<Task>>,
TaskPriorityComparer>
taskQueue_;
const RuntimeExecutor runtimeExecutor_;
mutable SchedulerPriority currentPriority_{SchedulerPriority::NormalPriority};
/*
* Counter indicating how many access to the runtime have been requested.
*/
mutable std::atomic<uint_fast8_t> runtimeAccessRequests_{0};
mutable std::atomic_bool isSynchronous_{false};
void startWorkLoop(jsi::Runtime& runtime) const;
/*
* Schedules a work loop unless it has been already scheduled
* This is to avoid unnecessary calls to `runtimeExecutor`.
*/
void scheduleWorkLoopIfNecessary() const;
void executeTask(
jsi::Runtime& runtime,
const std::shared_ptr<Task>& task,
bool didUserCallbackTimeout) const;
/*
* Returns a time point representing the current point in time. May be called
* from multiple threads.
*/
std::function<RuntimeSchedulerTimePoint()> now_;
/*
* Flag indicating if callback on JavaScript queue has been
* scheduled.
*/
mutable std::atomic_bool isWorkLoopScheduled_{false};
/*
* This flag is set while performing work, to prevent re-entrancy.
*/
mutable std::atomic_bool isPerformingWork_{false};
// Actual implementation, stored as a unique pointer to simplify memory
// management.
std::unique_ptr<RuntimeSchedulerBase> runtimeSchedulerImpl_;
};
} // namespace facebook::react

Просмотреть файл

@ -0,0 +1,193 @@
/*
* 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.
*/
#include "RuntimeScheduler_Legacy.h"
#include "SchedulerPriorityUtils.h"
#include <react/renderer/debug/SystraceSection.h>
#include <utility>
#include "ErrorUtils.h"
namespace facebook::react {
#pragma mark - Public
RuntimeScheduler_Legacy::RuntimeScheduler_Legacy(
RuntimeExecutor runtimeExecutor,
std::function<RuntimeSchedulerTimePoint()> now)
: runtimeExecutor_(std::move(runtimeExecutor)), now_(std::move(now)) {}
void RuntimeScheduler_Legacy::scheduleWork(
RawCallback&& callback) const noexcept {
SystraceSection s("RuntimeScheduler::scheduleWork");
runtimeAccessRequests_ += 1;
runtimeExecutor_(
[this, callback = std::move(callback)](jsi::Runtime& runtime) {
SystraceSection s2("RuntimeScheduler::scheduleWork callback");
runtimeAccessRequests_ -= 1;
callback(runtime);
startWorkLoop(runtime);
});
}
std::shared_ptr<Task> RuntimeScheduler_Legacy::scheduleTask(
SchedulerPriority priority,
jsi::Function&& callback) noexcept {
auto expirationTime = now_() + timeoutForSchedulerPriority(priority);
auto task =
std::make_shared<Task>(priority, std::move(callback), expirationTime);
taskQueue_.push(task);
scheduleWorkLoopIfNecessary();
return task;
}
std::shared_ptr<Task> RuntimeScheduler_Legacy::scheduleTask(
SchedulerPriority priority,
RawCallback&& callback) noexcept {
auto expirationTime = now_() + timeoutForSchedulerPriority(priority);
auto task =
std::make_shared<Task>(priority, std::move(callback), expirationTime);
taskQueue_.push(task);
scheduleWorkLoopIfNecessary();
return task;
}
bool RuntimeScheduler_Legacy::getShouldYield() const noexcept {
return runtimeAccessRequests_ > 0;
}
bool RuntimeScheduler_Legacy::getIsSynchronous() const noexcept {
return isSynchronous_;
}
void RuntimeScheduler_Legacy::cancelTask(Task& task) noexcept {
task.callback.reset();
}
SchedulerPriority RuntimeScheduler_Legacy::getCurrentPriorityLevel()
const noexcept {
return currentPriority_;
}
RuntimeSchedulerTimePoint RuntimeScheduler_Legacy::now() const noexcept {
return now_();
}
void RuntimeScheduler_Legacy::executeNowOnTheSameThread(
RawCallback&& callback) {
SystraceSection s("RuntimeScheduler::executeNowOnTheSameThread");
runtimeAccessRequests_ += 1;
executeSynchronouslyOnSameThread_CAN_DEADLOCK(
runtimeExecutor_,
[this, callback = std::move(callback)](jsi::Runtime& runtime) {
SystraceSection s2(
"RuntimeScheduler::executeNowOnTheSameThread callback");
runtimeAccessRequests_ -= 1;
isSynchronous_ = true;
callback(runtime);
isSynchronous_ = false;
});
// Resume work loop if needed. In synchronous mode
// only expired tasks are executed. Tasks with lower priority
// might be still in the queue.
scheduleWorkLoopIfNecessary();
}
void RuntimeScheduler_Legacy::callExpiredTasks(jsi::Runtime& runtime) {
SystraceSection s("RuntimeScheduler::callExpiredTasks");
auto previousPriority = currentPriority_;
try {
while (!taskQueue_.empty()) {
auto topPriorityTask = taskQueue_.top();
auto now = now_();
auto didUserCallbackTimeout = topPriorityTask->expirationTime <= now;
if (!didUserCallbackTimeout) {
break;
}
executeTask(runtime, topPriorityTask, didUserCallbackTimeout);
}
} catch (jsi::JSError& error) {
handleFatalError(runtime, error);
}
currentPriority_ = previousPriority;
}
#pragma mark - Private
void RuntimeScheduler_Legacy::scheduleWorkLoopIfNecessary() const {
if (!isWorkLoopScheduled_ && !isPerformingWork_) {
isWorkLoopScheduled_ = true;
runtimeExecutor_([this](jsi::Runtime& runtime) {
isWorkLoopScheduled_ = false;
startWorkLoop(runtime);
});
}
}
void RuntimeScheduler_Legacy::startWorkLoop(jsi::Runtime& runtime) const {
SystraceSection s("RuntimeScheduler::startWorkLoop");
auto previousPriority = currentPriority_;
isPerformingWork_ = true;
try {
while (!taskQueue_.empty()) {
auto topPriorityTask = taskQueue_.top();
auto now = now_();
auto didUserCallbackTimeout = topPriorityTask->expirationTime <= now;
if (!didUserCallbackTimeout && getShouldYield()) {
// This currentTask hasn't expired, and we need to yield.
break;
}
executeTask(runtime, topPriorityTask, didUserCallbackTimeout);
}
} catch (jsi::JSError& error) {
handleFatalError(runtime, error);
}
currentPriority_ = previousPriority;
isPerformingWork_ = false;
}
void RuntimeScheduler_Legacy::executeTask(
jsi::Runtime& runtime,
const std::shared_ptr<Task>& task,
bool didUserCallbackTimeout) const {
SystraceSection s(
"RuntimeScheduler::executeTask",
"priority",
serialize(task->priority),
"didUserCallbackTimeout",
didUserCallbackTimeout);
currentPriority_ = task->priority;
auto result = task->execute(runtime, didUserCallbackTimeout);
if (result.isObject() && result.getObject(runtime).isFunction(runtime)) {
task->callback = result.getObject(runtime).getFunction(runtime);
} else {
if (taskQueue_.top() == task) {
taskQueue_.pop();
}
}
}
} // namespace facebook::react

Просмотреть файл

@ -0,0 +1,161 @@
/*
* 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 <ReactCommon/RuntimeExecutor.h>
#include <react/renderer/runtimescheduler/RuntimeScheduler.h>
#include <react/renderer/runtimescheduler/RuntimeSchedulerClock.h>
#include <react/renderer/runtimescheduler/Task.h>
#include <atomic>
#include <memory>
#include <queue>
namespace facebook::react {
class RuntimeScheduler_Legacy final : public RuntimeSchedulerBase {
public:
RuntimeScheduler_Legacy(
RuntimeExecutor runtimeExecutor,
std::function<RuntimeSchedulerTimePoint()> now =
RuntimeSchedulerClock::now);
/*
* Not copyable.
*/
RuntimeScheduler_Legacy(const RuntimeScheduler_Legacy&) = delete;
RuntimeScheduler_Legacy& operator=(const RuntimeScheduler_Legacy&) = delete;
/*
* Not movable.
*/
RuntimeScheduler_Legacy(RuntimeScheduler_Legacy&&) = delete;
RuntimeScheduler_Legacy& operator=(RuntimeScheduler_Legacy&&) = delete;
void scheduleWork(RawCallback&& callback) const noexcept override;
/*
* Grants access to the runtime synchronously on the caller's thread.
*
* Shouldn't be called directly. it is expected to be used
* by dispatching a synchronous event via event emitter in your native
* component.
*/
void executeNowOnTheSameThread(RawCallback&& callback) override;
/*
* Adds a JavaScript callback to priority queue with given priority.
* Triggers workloop if needed.
*
* Thread synchronization must be enforced externally.
*/
std::shared_ptr<Task> scheduleTask(
SchedulerPriority priority,
jsi::Function&& callback) noexcept override;
std::shared_ptr<Task> scheduleTask(
SchedulerPriority priority,
RawCallback&& callback) noexcept override;
/*
* Cancelled task will never be executed.
*
* Operates on JSI object.
* Thread synchronization must be enforced externally.
*/
void cancelTask(Task& task) noexcept override;
/*
* Return value indicates if host platform has a pending access to the
* runtime.
*
* Can be called from any thread.
*/
bool getShouldYield() const noexcept override;
/*
* Return value informs if the current task is executed inside synchronous
* block.
*
* Can be called from any thread.
*/
bool getIsSynchronous() const noexcept override;
/*
* Returns value of currently executed task. Designed to be called from React.
*
* Thread synchronization must be enforced externally.
*/
SchedulerPriority getCurrentPriorityLevel() const noexcept override;
/*
* Returns current monotonic time. This time is not related to wall clock
* time.
*
* Thread synchronization must be enforced externally.
*/
RuntimeSchedulerTimePoint now() const noexcept override;
/*
* Expired task is a task that should have been already executed. Designed to
* be called in the event pipeline after an event is dispatched to React.
* React may schedule events with immediate priority which need to be handled
* before the next event is sent to React.
*
* Thread synchronization must be enforced externally.
*/
void callExpiredTasks(jsi::Runtime& runtime) override;
private:
mutable std::priority_queue<
std::shared_ptr<Task>,
std::vector<std::shared_ptr<Task>>,
TaskPriorityComparer>
taskQueue_;
const RuntimeExecutor runtimeExecutor_;
mutable SchedulerPriority currentPriority_{SchedulerPriority::NormalPriority};
/*
* Counter indicating how many access to the runtime have been requested.
*/
mutable std::atomic<uint_fast8_t> runtimeAccessRequests_{0};
mutable std::atomic_bool isSynchronous_{false};
void startWorkLoop(jsi::Runtime& runtime) const;
/*
* Schedules a work loop unless it has been already scheduled
* This is to avoid unnecessary calls to `runtimeExecutor`.
*/
void scheduleWorkLoopIfNecessary() const;
void executeTask(
jsi::Runtime& runtime,
const std::shared_ptr<Task>& task,
bool didUserCallbackTimeout) const;
/*
* Returns a time point representing the current point in time. May be called
* from multiple threads.
*/
std::function<RuntimeSchedulerTimePoint()> now_;
/*
* Flag indicating if callback on JavaScript queue has been
* scheduled.
*/
mutable std::atomic_bool isWorkLoopScheduled_{false};
/*
* This flag is set while performing work, to prevent re-entrancy.
*/
mutable std::atomic_bool isPerformingWork_{false};
};
} // namespace facebook::react

Просмотреть файл

@ -16,7 +16,7 @@
namespace facebook::react {
class RuntimeScheduler;
class RuntimeScheduler_Legacy;
class TaskPriorityComparer;
using RawCallback = std::function<void(jsi::Runtime&)>;
@ -33,7 +33,7 @@ struct Task final : public jsi::NativeState {
std::chrono::steady_clock::time_point expirationTime);
private:
friend RuntimeScheduler;
friend RuntimeScheduler_Legacy;
friend TaskPriorityComparer;
SchedulerPriority priority;