Add support for synchronous completeRoot

Summary:
changelog: [internal]

Exposes a new flag on RuntimeScheduler: `unstable_getIsSynchronous`. Flag indicates if the current code is run synchronously and therefore commit phase should be synchronous.

Unit tests will be added later, to keep this diff short. This code path is not executed yet.

Reviewed By: mdvacca, ShikaSD

Differential Revision: D32677814

fbshipit-source-id: e01d4fff7e716d627ff99fe104965851138c3aef
This commit is contained in:
Samuel Susla
2021-12-07 12:11:24 -08:00
committed by Facebook GitHub Bot
parent c0710244b3
commit fcda1ac514
9 changed files with 124 additions and 33 deletions
@@ -24603,6 +24603,6 @@ if (
) {
__REACT_DEVTOOLS_GLOBAL_HOOK__.registerInternalModuleStop(new Error());
}
})();
}
@@ -58,6 +58,10 @@ bool RuntimeScheduler::getShouldYield() const noexcept {
return shouldYield_;
}
bool RuntimeScheduler::getIsSynchronous() const noexcept {
return isSynchronous_;
}
void RuntimeScheduler::cancelTask(const std::shared_ptr<Task> &task) noexcept {
task->callback.reset();
}
@@ -75,14 +79,31 @@ void RuntimeScheduler::setEnableYielding(bool enableYielding) {
}
void RuntimeScheduler::executeNowOnTheSameThread(
std::function<void(jsi::Runtime &runtime)> callback) const {
std::function<void(jsi::Runtime &runtime)> callback) {
shouldYield_ = true;
executeSynchronouslyOnSameThread_CAN_DEADLOCK(
runtimeExecutor_,
[callback = std::move(callback)](jsi::Runtime &runtime) {
callback(runtime);
[this, callback = std::move(callback)](jsi::Runtime &runtime) {
shouldYield_ = false;
auto task = jsi::Function::createFromHostFunction(
runtime,
jsi::PropNameID::forUtf8(runtime, ""),
3,
[callback = std::move(callback)](
jsi::Runtime &runtime,
jsi::Value const &,
jsi::Value const *arguments,
size_t) -> jsi::Value {
callback(runtime);
return jsi::Value::undefined();
});
assert(!isPerformingWork_);
this->scheduleTask(
SchedulerPriority::ImmediatePriority, std::move(task));
isSynchronous_ = true;
startWorkLoop(runtime);
isSynchronous_ = false;
});
shouldYield_ = false;
}
#pragma mark - Private
@@ -45,8 +45,14 @@ class RuntimeScheduler final {
* component.
*/
void executeNowOnTheSameThread(
std::function<void(jsi::Runtime &runtime)> callback) const;
std::function<void(jsi::Runtime &runtime)> callback);
/*
* 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);
@@ -55,6 +61,8 @@ class RuntimeScheduler final {
bool getShouldYield() const noexcept;
bool getIsSynchronous() const noexcept;
SchedulerPriority getCurrentPriorityLevel() const noexcept;
RuntimeSchedulerTimePoint now() const noexcept;
@@ -71,6 +79,7 @@ class RuntimeScheduler final {
RuntimeExecutor const runtimeExecutor_;
mutable SchedulerPriority currentPriority_{SchedulerPriority::NormalPriority};
mutable std::atomic_bool shouldYield_{false};
mutable std::atomic_bool isSynchronous_{false};
void startWorkLoop(jsi::Runtime &runtime) const;
@@ -84,7 +93,7 @@ class RuntimeScheduler final {
* Flag indicating if callback on JavaScript queue has been
* scheduled.
*/
std::atomic_bool isCallbackScheduled_{false};
mutable std::atomic_bool isCallbackScheduled_{false};
/*
* Flag indicating if yielding is enabled.
@@ -42,10 +42,28 @@ RuntimeSchedulerBinding::createAndInstallIfNeeded(
return runtimeSchedulerObject.getHostObject<RuntimeSchedulerBinding>(runtime);
}
std::shared_ptr<RuntimeSchedulerBinding> RuntimeSchedulerBinding::getBinding(
jsi::Runtime &runtime) {
auto runtimeSchedulerModuleName = "nativeRuntimeScheduler";
auto runtimeSchedulerValue =
runtime.global().getProperty(runtime, runtimeSchedulerModuleName);
if (runtimeSchedulerValue.isUndefined()) {
return nullptr;
}
auto runtimeSchedulerObject = runtimeSchedulerValue.asObject(runtime);
return runtimeSchedulerObject.getHostObject<RuntimeSchedulerBinding>(runtime);
}
RuntimeSchedulerBinding::RuntimeSchedulerBinding(
std::shared_ptr<RuntimeScheduler> const &runtimeScheduler)
: runtimeScheduler_(runtimeScheduler) {}
bool RuntimeSchedulerBinding::getIsSynchronous() const {
return runtimeScheduler_->getIsSynchronous();
}
jsi::Value RuntimeSchedulerBinding::get(
jsi::Runtime &runtime,
jsi::PropNameID const &name) {
@@ -31,11 +31,20 @@ class RuntimeSchedulerBinding : public jsi::HostObject {
jsi::Runtime &runtime,
std::shared_ptr<RuntimeScheduler> const &runtimeScheduler);
/*
* Returns a shared pointer to RuntimeSchedulerBinding previously installed
* into a runtime. Thread synchronization must be enforced externally.
*/
static std::shared_ptr<RuntimeSchedulerBinding> getBinding(
jsi::Runtime &runtime);
/*
* `jsi::HostObject` specific overloads.
*/
jsi::Value get(jsi::Runtime &runtime, jsi::PropNameID const &name) override;
bool getIsSynchronous() const;
private:
std::shared_ptr<RuntimeScheduler> runtimeScheduler_;
};
@@ -61,5 +61,21 @@ static inline std::chrono::milliseconds timeoutForSchedulerPriority(
}
}
static inline std::string debugValueForSchedulerPriority(
SchedulerPriority schedulerPriority) {
switch (schedulerPriority) {
case SchedulerPriority::ImmediatePriority:
return "SchedulerPriority::ImmediatePriority";
case SchedulerPriority::UserBlockingPriority:
return "SchedulerPriority::UserBlockingPriority";
case SchedulerPriority::NormalPriority:
return "SchedulerPriority::NormalPriority";
case SchedulerPriority::LowPriority:
return "SchedulerPriority::LowPriority";
case SchedulerPriority::IdlePriority:
return "SchedulerPriority::IdlePriority";
}
}
} // namespace react
} // namespace facebook
@@ -32,6 +32,7 @@ LOCAL_SHARED_LIBRARIES := \
libreact_render_debug \
libreact_render_graphics \
libreact_render_leakchecker \
libreact_render_runtimescheduler \
libreact_render_mounting \
libreact_config \
librrc_root \
@@ -49,6 +50,7 @@ $(call import-module,react/renderer/components/view)
$(call import-module,react/renderer/componentregistry)
$(call import-module,react/renderer/core)
$(call import-module,react/renderer/leakchecker)
$(call import-module,react/renderer/runtimescheduler)
$(call import-module,react/renderer/debug)
$(call import-module,react/renderer/graphics)
$(call import-module,react/renderer/mounting)
@@ -60,6 +60,7 @@ rn_xplat_cxx_library(
react_native_xplat_target("react/renderer/componentregistry:componentregistry"),
react_native_xplat_target("react/renderer/debug:debug"),
react_native_xplat_target("runtimeexecutor:runtimeexecutor"),
react_native_xplat_target("react/renderer/runtimescheduler:runtimescheduler"),
],
)
@@ -12,6 +12,7 @@
#include <react/debug/react_native_assert.h>
#include <react/renderer/core/LayoutableShadowNode.h>
#include <react/renderer/debug/SystraceSection.h>
#include <react/renderer/runtimescheduler/RuntimeSchedulerBinding.h>
#include <react/renderer/uimanager/primitives.h>
namespace facebook::react {
@@ -543,33 +544,47 @@ jsi::Value UIManagerBinding::get(
jsi::Value const &thisValue,
jsi::Value const *arguments,
size_t count) noexcept -> jsi::Value {
auto runtimeSchedulerBinding =
RuntimeSchedulerBinding::getBinding(runtime);
auto surfaceId = surfaceIdFromValue(runtime, arguments[0]);
auto weakShadowNodeList =
weakShadowNodeListFromValue(runtime, arguments[1]);
static std::atomic_uint_fast8_t completeRootEventCounter{0};
static std::atomic_uint_fast32_t mostRecentSurfaceId{0};
completeRootEventCounter += 1;
mostRecentSurfaceId = surfaceId;
uiManager->backgroundExecutor_(
[weakUIManager,
weakShadowNodeList,
surfaceId,
eventCount = completeRootEventCounter.load()] {
auto shouldYield = [=]() -> bool {
// If `completeRootEventCounter` was incremented, another
// `completeSurface` call has been scheduled and current
// `completeSurface` should yield to it.
return completeRootEventCounter > eventCount &&
mostRecentSurfaceId == surfaceId;
};
auto shadowNodeList =
shadowNodeListFromWeakList(weakShadowNodeList);
auto strongUIManager = weakUIManager.lock();
if (shadowNodeList && strongUIManager) {
strongUIManager->completeSurface(
surfaceId, shadowNodeList, {true, shouldYield});
}
});
if (runtimeSchedulerBinding &&
runtimeSchedulerBinding->getIsSynchronous()) {
auto weakShadowNodeList =
weakShadowNodeListFromValue(runtime, arguments[1]);
auto shadowNodeList =
shadowNodeListFromWeakList(weakShadowNodeList);
if (shadowNodeList) {
uiManager->completeSurface(surfaceId, shadowNodeList, {true});
}
} else {
auto weakShadowNodeList =
weakShadowNodeListFromValue(runtime, arguments[1]);
static std::atomic_uint_fast8_t completeRootEventCounter{0};
static std::atomic_uint_fast32_t mostRecentSurfaceId{0};
completeRootEventCounter += 1;
mostRecentSurfaceId = surfaceId;
uiManager->backgroundExecutor_(
[weakUIManager,
weakShadowNodeList,
surfaceId,
eventCount = completeRootEventCounter.load()] {
auto shouldYield = [=]() -> bool {
// If `completeRootEventCounter` was incremented, another
// `completeSurface` call has been scheduled and current
// `completeSurface` should yield to it.
return completeRootEventCounter > eventCount &&
mostRecentSurfaceId == surfaceId;
};
auto shadowNodeList =
shadowNodeListFromWeakList(weakShadowNodeList);
auto strongUIManager = weakUIManager.lock();
if (shadowNodeList && strongUIManager) {
strongUIManager->completeSurface(
surfaceId, shadowNodeList, {true, shouldYield});
}
});
}
return jsi::Value::undefined();
});