Files
react-native/ReactCommon/react/renderer/runtimescheduler/RuntimeScheduler.cpp
T
Samuel Susla 0857238633 Pass didUserCallbackTimeout argument to Scheduler's callback
Summary:
changelog: [internal]

RuntimeScheduler's callbacks previously did not pass down argument `didUserCallbackTimeout`. This was intentionally left out because React team intended to remove it. But, as it turns out, it was not removed and it is needed for Offscreen to not enter infinite render loop. In React, it is used here: https://github.com/facebook/react/blob/main/packages/react-reconciler/src/ReactFiberWorkLoop.new.js#L1022-L1028

Reviewed By: ryancat

Differential Revision: D40060377

fbshipit-source-id: c45719fbbd0275ab2bcf9a2bbf0191aaa96010cc
2022-10-05 10:35:33 -07:00

165 lines
4.5 KiB
C++

/*
* 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.h"
#include <utility>
#include "ErrorUtils.h"
namespace facebook {
namespace react {
#pragma mark - Public
RuntimeScheduler::RuntimeScheduler(
RuntimeExecutor runtimeExecutor,
std::function<RuntimeSchedulerTimePoint()> now)
: runtimeExecutor_(std::move(runtimeExecutor)), now_(std::move(now)) {}
void RuntimeScheduler::scheduleWork(
std::function<void(jsi::Runtime &)> callback) const {
runtimeAccessRequests_ += 1;
runtimeExecutor_(
[this, callback = std::move(callback)](jsi::Runtime &runtime) {
runtimeAccessRequests_ -= 1;
callback(runtime);
startWorkLoop(runtime);
});
}
std::shared_ptr<Task> RuntimeScheduler::scheduleTask(
SchedulerPriority priority,
jsi::Function callback) {
auto expirationTime = now() + timeoutForSchedulerPriority(priority);
auto task =
std::make_shared<Task>(priority, std::move(callback), expirationTime);
taskQueue_.push(task);
scheduleWorkLoopIfNecessary();
return task;
}
bool RuntimeScheduler::getShouldYield() const noexcept {
return runtimeAccessRequests_ > 0;
}
bool RuntimeScheduler::getIsSynchronous() const noexcept {
return isSynchronous_;
}
void RuntimeScheduler::cancelTask(Task &task) noexcept {
task.callback.reset();
}
SchedulerPriority RuntimeScheduler::getCurrentPriorityLevel() const noexcept {
return currentPriority_;
}
RuntimeSchedulerTimePoint RuntimeScheduler::now() const noexcept {
return now_();
}
void RuntimeScheduler::executeNowOnTheSameThread(
std::function<void(jsi::Runtime &runtime)> callback) {
runtimeAccessRequests_ += 1;
executeSynchronouslyOnSameThread_CAN_DEADLOCK(
runtimeExecutor_,
[this, callback = std::move(callback)](jsi::Runtime &runtime) {
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::callExpiredTasks(jsi::Runtime &runtime) {
auto previousPriority = currentPriority_;
try {
while (!taskQueue_.empty()) {
auto topPriorityTask = taskQueue_.top();
auto now = now_();
auto didUserCallbackTimeout = topPriorityTask->expirationTime <= now;
if (!didUserCallbackTimeout) {
break;
}
currentPriority_ = topPriorityTask->priority;
auto result = topPriorityTask->execute(runtime, didUserCallbackTimeout);
if (result.isObject() && result.getObject(runtime).isFunction(runtime)) {
topPriorityTask->callback =
result.getObject(runtime).getFunction(runtime);
} else {
if (taskQueue_.top() == topPriorityTask) {
taskQueue_.pop();
}
}
}
} 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 {
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;
}
currentPriority_ = topPriorityTask->priority;
auto result = topPriorityTask->execute(runtime, didUserCallbackTimeout);
if (result.isObject() && result.getObject(runtime).isFunction(runtime)) {
topPriorityTask->callback =
result.getObject(runtime).getFunction(runtime);
} else {
if (taskQueue_.top() == topPriorityTask) {
taskQueue_.pop();
}
}
}
} catch (jsi::JSError &error) {
handleFatalError(runtime, error);
}
currentPriority_ = previousPriority;
isPerformingWork_ = false;
}
} // namespace react
} // namespace facebook