react-native-macos/ReactCommon/fabric/uimanager/Scheduler.h

117 строки
3.7 KiB
C
Исходник Обычный вид История

// Copyright (c) Facebook, Inc. and its affiliates.
// This source code is licensed under the MIT license found in the
// LICENSE file in the root directory of this source tree.
#pragma once
#include <memory>
#include <mutex>
#include <react/components/root/RootComponentDescriptor.h>
#include <react/config/ReactNativeConfig.h>
#include <react/core/ComponentDescriptor.h>
#include <react/core/LayoutConstraints.h>
#include <react/mounting/ShadowTree.h>
#include <react/mounting/ShadowTreeDelegate.h>
#include <react/uimanager/ComponentDescriptorFactory.h>
#include <react/uimanager/ComponentDescriptorRegistry.h>
#include <react/uimanager/SchedulerDelegate.h>
#include <react/uimanager/SchedulerToolbox.h>
#include <react/uimanager/UIManagerBinding.h>
#include <react/uimanager/UIManagerDelegate.h>
#include <react/utils/ContextContainer.h>
#include <react/utils/RuntimeExecutor.h>
namespace facebook {
namespace react {
/*
* Scheduler coordinates Shadow Tree updates and event flows.
*/
class Scheduler final : public UIManagerDelegate, public ShadowTreeDelegate {
public:
Scheduler(SchedulerToolbox schedulerToolbox, SchedulerDelegate *delegate);
~Scheduler();
#pragma mark - Surface Management
void startSurface(
SurfaceId surfaceId,
const std::string &moduleName,
const folly::dynamic &initialProps,
const LayoutConstraints &layoutConstraints = {},
mostly working on Android + OTA Summary: It works great on iOS, and mostly works on Android, and is now OTA'able as part of the screen config! Haven't done template view yet. One remaining issue: Layout is borked on Android. I'm guessing the issue has to do with the timing of setting the constraints in `updateRootLayoutSpecs` and calling `mBinding.startSurface` which actually builds the shadow tree. If I try to call `updateRootLayoutSpecs` earlier, it just crashes immediately. Here's the layout it spits out, which clearly has -440 for the x of 420006, which is the RCTText component, causing it to get cut off on the left of the screen: ``` updateLayoutMountItem for reactTag: 420006 x: -440, y: -13, width: 931, height: 78 updateLayoutMountItem for reactTag: 420010 x: 26, y: 79, width: 0, height: 1651 updateLayoutMountItem for reactTag: 420012 x: 0, y: 26, width: 0, height: 158 updateLayoutMountItem for reactTag: 420016 x: 0, y: 210, width: 454, height: 454 updateLayoutMountItem for reactTag: 420018 x: 454, y: 210, width: 455, height: 454 updateLayoutMountItem for reactTag: 420022 x: 0, y: 690, width: 454, height: 454 updateLayoutMountItem for reactTag: 420024 x: 454, y: 690, width: 455, height: 454 updateLayoutMountItem for reactTag: 420028 x: 0, y: 1171, width: 454, height: 454 updateLayoutMountItem for reactTag: 420030 x: 454, y: 1171, width: 455, height: 454 updateLayoutMountItem for reactTag: 420032 x: 0, y: 1651, width: 0, height: 0 ``` Reviewed By: mdvacca Differential Revision: D12813192 fbshipit-source-id: 450d646af4883ff25184141721351da67b091b7c
2018-11-06 02:32:47 +03:00
const LayoutContext &layoutContext = {}) const;
void renderTemplateToSurface(
SurfaceId surfaceId,
const std::string &uiTemplate);
void stopSurface(SurfaceId surfaceId) const;
Size measureSurface(
SurfaceId surfaceId,
const LayoutConstraints &layoutConstraints,
const LayoutContext &layoutContext) const;
/*
* Applies given `layoutConstraints` and `layoutContext` to a Surface.
* The user interface will be relaid out as a result. The operation will be
* performed synchronously (including mounting) if the method is called
* on the main thread.
* Can be called from any thread.
*/
void constraintSurfaceLayout(
SurfaceId surfaceId,
const LayoutConstraints &layoutConstraints,
const LayoutContext &layoutContext) const;
const ComponentDescriptor &getComponentDescriptor(ComponentHandle handle);
#pragma mark - Delegate
/*
* Sets and gets the Scheduler's delegate.
* The delegate is stored as a raw pointer, so the owner must null
* the pointer before being destroyed.
*/
void setDelegate(SchedulerDelegate *delegate);
SchedulerDelegate *getDelegate() const;
#pragma mark - UIManagerDelegate
void uiManagerDidFinishTransaction(
SurfaceId surfaceId,
const SharedShadowNodeUnsharedList &rootChildNodes) override;
void uiManagerDidCreateShadowNode(
const SharedShadowNode &shadowNode) override;
void uiManagerDidDispatchCommand(
const SharedShadowNode &shadowNode,
std::string const &commandName,
folly::dynamic const args) override;
void uiManagerDidSetJSResponder(
SurfaceId surfaceId,
const SharedShadowNode &shadowView,
bool blockNativeResponder) override;
void uiManagerDidClearJSResponder() override;
#pragma mark - ShadowTreeDelegate
void shadowTreeDidCommit(
ShadowTree const &shadowTree,
MountingCoordinator::Shared const &mountingCoordinator) const override;
private:
SchedulerDelegate *delegate_;
SharedComponentDescriptorRegistry componentDescriptorRegistry_;
std::unique_ptr<const RootComponentDescriptor> rootComponentDescriptor_;
ShadowTreeRegistry shadowTreeRegistry_;
RuntimeExecutor runtimeExecutor_;
Fabric: Changing retaining configuration among `UIManager`, `UIManagerBindging`, `Scheduler` and `EventDisaptcher` Summary: This diff changes the way how `UIManager`, `UIManagerBindging`, `Scheduler` and `EventDisaptcher` refer to each other which should help with stability and reliability. Here is the node that describes the details: # Retaining dilemma # Players We have many logical of moving pieces but most of them can be abstracted by following high-level components. * **Scheduler** `Scheduler` is the main representation of running React Native infrastructure. Creation of it means the creation of all React Native C++ subsystems (excluding RuntimeExecutor) and destruction of that means the destruction of all dependent parts. Both processes must be thread-safe. * **UIManager** UIManager is a module that contains the most high-level logic of managing shadow trees. All React Renderer calls are practically implemented there. * **UIManagerBinding** UIManagerBinding is a representation (aka `HostObject`) of UIManager in the JavaScript world. * **EventDispatcher** EventDispatcher is a class that implements all logic related to dispatching events: from calling event on any thread anywhere to executing a particular JavaScript handler responsible for handling that event. Instances of those classes have complex relationships in terms of owning each other, order of creation and destruction. The configuration of these relationships is dictated by a set of constraints that those classes need to satisfy to be constructed, accessed, and destructed in a hostile multithreaded environment. Messing with that can cause deadlocks, random crashes, suboptimal performance or memory leaks. Make sure you consider all constraints and requirements before changing that. # Goal We need to have a safe and reliable way to construct and destroy those objects (on any thread, in any random moment). Keep in mind that all of those objects are being accessed from random threads and have random states in any particular moment. Switching threads happens all the time, so having some state in one place does not guarantee any state in other places. # Caveats Let's discuss all concrete constrains that the moving pieces have to satisfy. * **UIManagerBinding is a HostObject** Practically that means: 1. It must be constructed "on JavaScript thread" (with exclusive access to JavaScript VM); 2. It must not be retained by other parts of the system because overliving the VM will cause a crash. 3. It can be destructed on any thread (VM does not give any guarantees here). The particular configuration guarantees that the destruction cannot be run concurrently with any JS execution though (because we never clear the reference to the host object from JavaScript side). * **UIManager needs to be connected with UIManagerBinding and vice-versa** Those to modules call each other to perform some UI updates or deliver events. * **Scheduler can be deallocated on any thread at any time** Timing and thread are up to the application side. The Scheduler must be resilient to that. * **EventDispatcher can call UIManager at any time** Luckily, that happens only on JavaScript thread. * **Using weak pointers cames at a cost** `std::weak_ptr` is a concept for managing the non-owning relationships in a safe manner. Dereferencing such pointers cames at a cost (additional object construction and atomic counters bumps). So, we should use that carefully; we cannot use shared and week pointers everywhere and assume that will work magically. # How does this blow up? Without describing the current configuration, here are a variety of cases that we currently observe. 1. `Scheduler` was deallocated and destroyed UIManager but VM is still running. The VM calls the UIManagerBinding, UIManagerBinding calls a method on already deallocated UIManager. Boom. 2. VM is being deallocated and deletes all host object as part of this process. Some UI event is sill in flight on some thread. The event retains UIManagerBinding via UIManager. VM cannot destroy UIManagerBinding because it's being retained. Boom. 3. VM was deallocated. `Scheduler` was deallocated. But some native state update is still in flight. It retains EventDispatcher and eventually trying to access some shadow tree that was retained by Scheduler and already dead. Boom. That's pretty much routine endless nightmare of any low-level framework. Luckily, the good proper decisions (and iterating on that!) can solve that. # Proposed configuration The configuration is based on those ideas: 1. Never retain `UIManagerBinging`. 2. Never recreate `UIManagerBinging`. Create once and load lazily from JS environment on demand. 3. Consider UIManager as an object with shared ownership between JS and native. That object must be able to overlive native infra or JS VM. 4. Use EventDispatcher as a single weak representation of the JavaScript world; Never retain it strongly except by the Scheduler. 5. `UIManagerBinging` and `UIManager` can be attached or detached. `UIManagerBinging` retains `UIManager`, `UIManager` does not retain `UIManagerBinging` back. Destroying `UIManagerBinging` nulls the raw pointer to that from `UIManager`. 6. All calls from native to JavaScript can validate the pointer from `UIManager` to `UIManagerBinging` to check that the call is possible. All that calls happen on JavaScript thread. ## Stages * **Creation process** Creation Scheduler creates `UIManager` and scheduler asynchronous call to JavaScript to create `UIManagerBinding` and attach them. At the same time `Scheduler` creates `EventDispatcher` and makes it retains `UIManager`. * **JavaScript-to-native invocation** `UIManagerBinding` has a shared pointer to `UIManager` and can cheaply and safely verify that the pointer is not nullptr. Any mutation of this pointer happens on the JavaScript thread or effectively on VM destruction (non-concurrently). * **Native-to-JavaScript invocation** The invocation starts from retaining `EventDispatcher` (converting a weak pointer to strong one), that retains `UIManager`. Later, on the JavaScript thread, `UIManager` checks the raw pointer to `UIManagerBinding` to verify that the call can be performed safely. # Easy ways to break the fragile balance - Never retain `EventDispatcher` as a shared pointer. That causes a leak of UIManager and associated resources. - Access a shared pointer to `UIManager` by value only. The simple way to break that is to specify `[=]` capture block for a lambda and access an instance variable pointing to a `UIManager` (that does not retain the pointer; make a copy on the stack and copy that to the lambda). Reviewed By: JoshuaGross Differential Revision: D17120333 fbshipit-source-id: 83138657683e91ceb2f48f18f30e745199c83e82
2019-08-31 04:21:59 +03:00
std::shared_ptr<UIManager> uiManager_;
std::shared_ptr<const ReactNativeConfig> reactNativeConfig_;
EventDispatcher::Shared eventDispatcher_;
};
} // namespace react
} // namespace facebook