Skip to content

Commit

Permalink
Create proxy for RuntimeScheduler to allow us to use a forked version
Browse files Browse the repository at this point in the history
Differential Revision: D49316880

fbshipit-source-id: 3aa8406ad5f3a5c196d418f3e8df423c6ba15d2f
  • Loading branch information
rubennorte authored and facebook-github-bot committed Oct 17, 2023
1 parent fe74689 commit 51200c1
Show file tree
Hide file tree
Showing 5 changed files with 408 additions and 196 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@
*/

#include "RuntimeScheduler.h"
#include "RuntimeScheduler_Legacy.h"
#include "SchedulerPriorityUtils.h"

#include <react/renderer/debug/SystraceSection.h>
Expand All @@ -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
Original file line number Diff line number Diff line change
Expand Up @@ -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.
*/
Expand All @@ -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.
Expand All @@ -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.
Expand All @@ -53,50 +74,50 @@ 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.
*
* 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
* runtime.
*
* 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
* block.
*
* 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
* time.
*
* 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
Expand All @@ -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
Loading

0 comments on commit 51200c1

Please sign in to comment.