forked from facebook/react-native
-
Notifications
You must be signed in to change notification settings - Fork 1
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Create new version of RuntimeScheduler (facebook#40944)
Summary: Pull Request resolved: facebook#40944 ## 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] Differential Revision: D49316881 fbshipit-source-id: 5e8f1563b7d1c34641c83667ac2b381ae4d8938c
- Loading branch information
1 parent
6e2a947
commit f7b5a4d
Showing
9 changed files
with
1,198 additions
and
15 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
249 changes: 249 additions & 0 deletions
249
...ages/react-native/ReactCommon/react/renderer/runtimescheduler/RuntimeScheduler_Modern.cpp
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,249 @@ | ||
/* | ||
* 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_Modern.h" | ||
#include "SchedulerPriorityUtils.h" | ||
|
||
#include <react/renderer/debug/SystraceSection.h> | ||
#include <utility> | ||
#include "ErrorUtils.h" | ||
|
||
namespace facebook::react { | ||
|
||
#pragma mark - Public | ||
|
||
RuntimeScheduler_Modern::RuntimeScheduler_Modern( | ||
RuntimeExecutor runtimeExecutor, | ||
std::function<RuntimeSchedulerTimePoint()> now) | ||
: runtimeExecutor_(std::move(runtimeExecutor)), now_(std::move(now)) {} | ||
|
||
void RuntimeScheduler_Modern::scheduleWork( | ||
RawCallback&& callback) const noexcept { | ||
SystraceSection s("RuntimeScheduler::scheduleWork"); | ||
scheduleTask(SchedulerPriority::ImmediatePriority, std::move(callback)); | ||
} | ||
|
||
std::shared_ptr<Task> RuntimeScheduler_Modern::scheduleTask( | ||
SchedulerPriority priority, | ||
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); | ||
|
||
scheduleTask(task); | ||
|
||
return task; | ||
} | ||
|
||
std::shared_ptr<Task> RuntimeScheduler_Modern::scheduleTask( | ||
SchedulerPriority priority, | ||
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); | ||
|
||
scheduleTask(task); | ||
|
||
return task; | ||
} | ||
|
||
bool RuntimeScheduler_Modern::getShouldYield() const noexcept { | ||
std::shared_lock lock(taskQueueMutex_); | ||
|
||
return syncTaskRequests_ > 0 || | ||
(!taskQueue_.empty() && taskQueue_.top() != currentTask_); | ||
} | ||
|
||
bool RuntimeScheduler_Modern::getIsSynchronous() const noexcept { | ||
return isSynchronous_; | ||
} | ||
|
||
void RuntimeScheduler_Modern::cancelTask(Task& task) noexcept { | ||
task.callback.reset(); | ||
} | ||
|
||
SchedulerPriority RuntimeScheduler_Modern::getCurrentPriorityLevel() | ||
const noexcept { | ||
return currentPriority_; | ||
} | ||
|
||
RuntimeSchedulerTimePoint RuntimeScheduler_Modern::now() const noexcept { | ||
return now_(); | ||
} | ||
|
||
void RuntimeScheduler_Modern::executeNowOnTheSameThread( | ||
RawCallback&& callback) { | ||
SystraceSection s("RuntimeScheduler::executeNowOnTheSameThread"); | ||
|
||
syncTaskRequests_++; | ||
|
||
executeSynchronouslyOnSameThread_CAN_DEADLOCK( | ||
runtimeExecutor_, | ||
[this, callback = std::move(callback)](jsi::Runtime& runtime) mutable { | ||
SystraceSection s2( | ||
"RuntimeScheduler::executeNowOnTheSameThread callback"); | ||
|
||
syncTaskRequests_--; | ||
|
||
isSynchronous_ = true; | ||
|
||
auto currentTime = now_(); | ||
auto priority = SchedulerPriority::ImmediatePriority; | ||
auto expirationTime = | ||
currentTime + timeoutForSchedulerPriority(priority); | ||
auto task = std::make_shared<Task>( | ||
priority, std::move(callback), expirationTime); | ||
|
||
executeTask(runtime, task, currentTime); | ||
|
||
isSynchronous_ = false; | ||
}); | ||
|
||
bool shouldScheduleWorkLoop = false; | ||
|
||
{ | ||
std::unique_lock lock(taskQueueMutex_); | ||
|
||
if (!taskQueue_.empty() && !isWorkLoopScheduled_) { | ||
isWorkLoopScheduled_ = true; | ||
shouldScheduleWorkLoop = true; | ||
} | ||
} | ||
|
||
if (shouldScheduleWorkLoop) { | ||
scheduleWorkLoop(); | ||
} | ||
} | ||
|
||
// This will be replaced by microtasks | ||
void RuntimeScheduler_Modern::callExpiredTasks(jsi::Runtime& runtime) { | ||
SystraceSection s("RuntimeScheduler::callExpiredTasks"); | ||
startWorkLoop(runtime, true); | ||
} | ||
|
||
#pragma mark - Private | ||
|
||
void RuntimeScheduler_Modern::scheduleTask(std::shared_ptr<Task> task) const { | ||
bool shouldScheduleWorkLoop = false; | ||
|
||
{ | ||
std::unique_lock lock(taskQueueMutex_); | ||
|
||
if (taskQueue_.empty() && !isWorkLoopScheduled_) { | ||
isWorkLoopScheduled_ = true; | ||
shouldScheduleWorkLoop = true; | ||
} | ||
|
||
taskQueue_.push(task); | ||
} | ||
|
||
if (shouldScheduleWorkLoop) { | ||
scheduleWorkLoop(); | ||
} | ||
} | ||
|
||
void RuntimeScheduler_Modern::scheduleWorkLoop() const { | ||
runtimeExecutor_( | ||
[this](jsi::Runtime& runtime) { startWorkLoop(runtime, false); }); | ||
} | ||
|
||
void RuntimeScheduler_Modern::startWorkLoop( | ||
jsi::Runtime& runtime, | ||
bool onlyExpired) const { | ||
SystraceSection s("RuntimeScheduler::startWorkLoop"); | ||
|
||
auto previousPriority = currentPriority_; | ||
|
||
try { | ||
while (syncTaskRequests_ == 0) { | ||
auto currentTime = now_(); | ||
auto topPriorityTask = selectTask(currentTime, onlyExpired); | ||
|
||
if (!topPriorityTask) { | ||
// No pending work to do. | ||
// Events will restart the loop when necessary. | ||
break; | ||
} | ||
|
||
executeTask(runtime, topPriorityTask, currentTime); | ||
} | ||
} catch (jsi::JSError& error) { | ||
handleFatalError(runtime, error); | ||
} | ||
|
||
currentPriority_ = previousPriority; | ||
} | ||
|
||
std::shared_ptr<Task> RuntimeScheduler_Modern::selectTask( | ||
RuntimeSchedulerTimePoint currentTime, | ||
bool onlyExpired) const { | ||
std::unique_lock lock(taskQueueMutex_); | ||
|
||
// It's safe to reset the flag here, as its access is also synchronized with | ||
// the access to the task queue. | ||
isWorkLoopScheduled_ = false; | ||
|
||
// Skip executed tasks | ||
while (!taskQueue_.empty() && !taskQueue_.top()->callback) { | ||
taskQueue_.pop(); | ||
} | ||
|
||
if (!taskQueue_.empty()) { | ||
auto task = taskQueue_.top(); | ||
auto didUserCallbackTimeout = task->expirationTime <= currentTime; | ||
if (!onlyExpired || didUserCallbackTimeout) { | ||
return task; | ||
} | ||
} | ||
|
||
return nullptr; | ||
} | ||
|
||
void RuntimeScheduler_Modern::executeTask( | ||
jsi::Runtime& runtime, | ||
const std::shared_ptr<Task>& task, | ||
RuntimeSchedulerTimePoint currentTime) const { | ||
auto didUserCallbackTimeout = task->expirationTime <= currentTime; | ||
|
||
SystraceSection s( | ||
"RuntimeScheduler::executeTask", | ||
"priority", | ||
serialize(task->priority), | ||
"didUserCallbackTimeout", | ||
didUserCallbackTimeout); | ||
|
||
currentTask_ = task; | ||
currentPriority_ = task->priority; | ||
|
||
auto result = task->execute(runtime, didUserCallbackTimeout); | ||
|
||
if (result.isObject() && result.getObject(runtime).isFunction(runtime)) { | ||
// If the task returned a continuation callback, we re-assign it to the task | ||
// and keep the task in the queue. | ||
task->callback = result.getObject(runtime).getFunction(runtime); | ||
} | ||
|
||
// TODO execute microtasks | ||
// TODO report long tasks | ||
// TODO update rendering | ||
} | ||
|
||
} // namespace facebook::react |
Oops, something went wrong.