Skip to content

Commit

Permalink
Create new version of RuntimeScheduler (facebook#40944)
Browse files Browse the repository at this point in the history
Summary:

## Summary

This creates a new version of `RuntimeScheduler` that's intended to be backwards compatible but with a few notable changes:
1. `scheduleTask` is now thread-safe.
2. `scheduleWork` is now just an alias of `scheduleTask` with immediate priority (to preserve the yielding semantics it had over other tasks).
3. Yielding mechanism has changed, to make lower priority tasks to yield to higher priority tasks, instead of just yielding to `scheduleWork` and `executeNowOnTheSameThread`.

We don't expect this to have any impact in performance or user perceivable behavior, so we consider it a short-lived refactor. When we validate this assumptions in a complex application we'll delete the old version and only keep the fork.

## Motivation

The main motivation for this refactor is to reduce the amount of unnecessary interruptions of running tasks (via `shouldYield`) that are only used to schedule asynchronous tasks from native.

The `scheduleWork` method is the only available mechanism exposed to native APIs to schedule work in the JS thread (as the existing version of `scheduleTask` is only meant to be called from JS). This mechanism **always** asks for any running tasks in the scheduler to yield, so these tasks are always considered to have the highest priority. This makes sense for discrete user events, but not for many other use cases coming from native (e.g.: notifying network responses could be UserBlocking, Normal or Low depending on the use case).

We need a way to schedule tasks from native with other kinds of priorities, so we don't always have to interrupt what's currently executing if it has a higher priority than what we're scheduling.

## Changes

**General APIs:**

This centralizes scheduling in only 2 APIs in `RuntimeScheduler` (which already exist in the legacy version):
* `scheduleTask`, which is non-blocking for the caller and can be used from any thread. This always uses the task queue in the scheduler and a new yielding mechanism.
* `executeNowOnTheSameThread`, which is blocking for the caller and asks any task executing in the scheduler to yield. These tasks don't go through the task queue and instead queue through the existing synchronization mechanism in `RuntimeExecutor`. The yielding mechanism for these tasks is preserved.

`scheduleWork` will be deprecated and it's just an alias for `scheduleTask` with an immediate priority (to preserve a similar behavior).

**Yielding behavior:**

Before, tasks would only yield to tasks scheduled via `scheduleWork` and `executeNowOnTheSameThread` (those tasks didn't go through the task queue).

With this implementation, tasks would now yield to any task that has a higher position in the task queue. That means we reuse the existing mechanism to avoid lower priority tasks to never execute because higher priority tasks never stop coming.

All tasks would yield to requests for synchronous access (via `executeNowOnTheSameThread`) as did the current implementation.

Changelog: [internal]

Reviewed By: javache, sammy-SC

Differential Revision: D49316881
  • Loading branch information
rubennorte authored and facebook-github-bot committed Oct 18, 2023
1 parent a23f1cf commit b1e2a33
Show file tree
Hide file tree
Showing 8 changed files with 803 additions and 37 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@

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

#include <react/renderer/debug/SystraceSection.h>
Expand All @@ -15,11 +16,28 @@

namespace facebook::react {

namespace {
std::unique_ptr<RuntimeSchedulerBase> getRuntimeSchedulerImplementation(
RuntimeExecutor runtimeExecutor,
bool useModernRuntimeScheduler,
std::function<RuntimeSchedulerTimePoint()> now) {
if (useModernRuntimeScheduler) {
return std::make_unique<RuntimeScheduler_Modern>(
std::move(runtimeExecutor), std::move(now));
} else {
return std::make_unique<RuntimeScheduler_Legacy>(
std::move(runtimeExecutor), std::move(now));
}
}
} // namespace

RuntimeScheduler::RuntimeScheduler(
RuntimeExecutor runtimeExecutor,
bool useModernRuntimeScheduler,
std::function<RuntimeSchedulerTimePoint()> now)
: runtimeSchedulerImpl_(std::make_unique<RuntimeScheduler_Legacy>(
: runtimeSchedulerImpl_(getRuntimeSchedulerImplementation(
std::move(runtimeExecutor),
useModernRuntimeScheduler,
std::move(now))) {}

void RuntimeScheduler::scheduleWork(RawCallback&& callback) const noexcept {
Expand All @@ -28,13 +46,13 @@ void RuntimeScheduler::scheduleWork(RawCallback&& callback) const noexcept {

std::shared_ptr<Task> RuntimeScheduler::scheduleTask(
SchedulerPriority priority,
jsi::Function&& callback) noexcept {
jsi::Function&& callback) const noexcept {
return runtimeSchedulerImpl_->scheduleTask(priority, std::move(callback));
}

std::shared_ptr<Task> RuntimeScheduler::scheduleTask(
SchedulerPriority priority,
RawCallback&& callback) noexcept {
RawCallback&& callback) const noexcept {
return runtimeSchedulerImpl_->scheduleTask(priority, std::move(callback));
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -18,14 +18,20 @@ namespace facebook::react {
class RuntimeSchedulerBase {
public:
virtual ~RuntimeSchedulerBase() = default;
// FIXME(T167271466): remove `const` modified when the RuntimeScheduler
// refactor has been shipped.
virtual void scheduleWork(RawCallback&& callback) const noexcept = 0;
virtual void executeNowOnTheSameThread(RawCallback&& callback) = 0;
// FIXME(T167271466): remove `const` modified when the RuntimeScheduler
// refactor has been shipped.
virtual std::shared_ptr<Task> scheduleTask(
SchedulerPriority priority,
jsi::Function&& callback) noexcept = 0;
jsi::Function&& callback) const noexcept = 0;
// FIXME(T167271466): remove `const` modified when the RuntimeScheduler
// refactor has been shipped.
virtual std::shared_ptr<Task> scheduleTask(
SchedulerPriority priority,
RawCallback&& callback) noexcept = 0;
RawCallback&& callback) const noexcept = 0;
virtual void cancelTask(Task& task) noexcept = 0;
virtual bool getShouldYield() const noexcept = 0;
virtual bool getIsSynchronous() const noexcept = 0;
Expand All @@ -38,8 +44,9 @@ class RuntimeSchedulerBase {
// at runtime based on a feature flag.
class RuntimeScheduler final : RuntimeSchedulerBase {
public:
RuntimeScheduler(
explicit RuntimeScheduler(
RuntimeExecutor runtimeExecutor,
bool useModernRuntimeScheduler = false,
std::function<RuntimeSchedulerTimePoint()> now =
RuntimeSchedulerClock::now);

Expand Down Expand Up @@ -74,11 +81,11 @@ class RuntimeScheduler final : RuntimeSchedulerBase {
*/
std::shared_ptr<Task> scheduleTask(
SchedulerPriority priority,
jsi::Function&& callback) noexcept override;
jsi::Function&& callback) const noexcept override;

std::shared_ptr<Task> scheduleTask(
SchedulerPriority priority,
RawCallback&& callback) noexcept override;
RawCallback&& callback) const noexcept override;

/*
* Cancelled task will never be executed.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -38,7 +38,14 @@ void RuntimeScheduler_Legacy::scheduleWork(

std::shared_ptr<Task> RuntimeScheduler_Legacy::scheduleTask(
SchedulerPriority priority,
jsi::Function&& callback) noexcept {
jsi::Function&& callback) const noexcept {
SystraceSection s(
"RuntimeScheduler::scheduleTask",
"priority",
serialize(priority),
"callbackType",
"jsi::Function");

auto expirationTime = now_() + timeoutForSchedulerPriority(priority);
auto task =
std::make_shared<Task>(priority, std::move(callback), expirationTime);
Expand All @@ -51,7 +58,14 @@ std::shared_ptr<Task> RuntimeScheduler_Legacy::scheduleTask(

std::shared_ptr<Task> RuntimeScheduler_Legacy::scheduleTask(
SchedulerPriority priority,
RawCallback&& callback) noexcept {
RawCallback&& callback) const noexcept {
SystraceSection s(
"RuntimeScheduler::scheduleTask",
"priority",
serialize(priority),
"callbackType",
"RawCallback");

auto expirationTime = now_() + timeoutForSchedulerPriority(priority);
auto task =
std::make_shared<Task>(priority, std::move(callback), expirationTime);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@ namespace facebook::react {

class RuntimeScheduler_Legacy final : public RuntimeSchedulerBase {
public:
RuntimeScheduler_Legacy(
explicit RuntimeScheduler_Legacy(
RuntimeExecutor runtimeExecutor,
std::function<RuntimeSchedulerTimePoint()> now =
RuntimeSchedulerClock::now);
Expand Down Expand Up @@ -55,11 +55,11 @@ class RuntimeScheduler_Legacy final : public RuntimeSchedulerBase {
*/
std::shared_ptr<Task> scheduleTask(
SchedulerPriority priority,
jsi::Function&& callback) noexcept override;
jsi::Function&& callback) const noexcept override;

std::shared_ptr<Task> scheduleTask(
SchedulerPriority priority,
RawCallback&& callback) noexcept override;
RawCallback&& callback) const noexcept override;

/*
* Cancelled task will never be executed.
Expand Down
Loading

0 comments on commit b1e2a33

Please sign in to comment.