mirror of
https://github.com/facebook/react-native.git
synced 2025-11-01 09:14:26 +00:00
Fabric: New non-blocking treading model for ShadowTree
Summary: Instead of the whole family of commit* and complete* methods, now we have one single `commit` method which performs pre- and post-commit operations and swap pointers in a thread-safe manner. The `commit` operation is also exposing `revision` number and allows perform multiple commit attempts. `completeByReplacingShadowNode`, `measure` and `constraintLayout` are also going away to RootShadowNode class in the next commits. Why? * Nicer API; * No more recursive_mutex, no more problems with thread jumps; * All mutex locks are now leaf-locks, so no more deadlocks possible; * Exposing `revision` should help with debugging races. Reviewed By: sahrens Differential Revision: D13613942 fbshipit-source-id: 94e797d2f7860717847e823b5d97c4f7b35f08df
This commit is contained in:
committed by
Facebook Github Bot
parent
8f9ca2b9a0
commit
5a58ca4144
@@ -104,8 +104,14 @@ void Scheduler::renderTemplateToSurface(
|
||||
reactNativeConfig_);
|
||||
|
||||
shadowTreeRegistry_.visit(surfaceId, [=](const ShadowTree &shadowTree) {
|
||||
shadowTree.complete(
|
||||
std::make_shared<SharedShadowNodeList>(SharedShadowNodeList{tree}));
|
||||
return shadowTree.commit(
|
||||
[&](const SharedRootShadowNode &oldRootShadowNode) {
|
||||
return std::make_shared<RootShadowNode>(
|
||||
*oldRootShadowNode,
|
||||
ShadowNodeFragment{.children =
|
||||
std::make_shared<SharedShadowNodeList>(
|
||||
SharedShadowNodeList{tree})});
|
||||
});
|
||||
});
|
||||
} catch (const std::exception &e) {
|
||||
LOG(ERROR) << " >>>> EXCEPTION <<< rendering uiTemplate in "
|
||||
@@ -118,8 +124,13 @@ void Scheduler::stopSurface(SurfaceId surfaceId) const {
|
||||
|
||||
shadowTreeRegistry_.visit(surfaceId, [](const ShadowTree &shadowTree) {
|
||||
// As part of stopping the Surface, we have to commit an empty tree.
|
||||
shadowTree.complete(std::const_pointer_cast<SharedShadowNodeList>(
|
||||
ShadowNode::emptySharedShadowNodeSharedList()));
|
||||
return shadowTree.commit(
|
||||
[&](const SharedRootShadowNode &oldRootShadowNode) {
|
||||
return std::make_shared<RootShadowNode>(
|
||||
*oldRootShadowNode,
|
||||
ShadowNodeFragment{
|
||||
.children = ShadowNode::emptySharedShadowNodeSharedList()});
|
||||
});
|
||||
});
|
||||
|
||||
auto shadowTree = shadowTreeRegistry_.remove(surfaceId);
|
||||
@@ -152,9 +163,7 @@ void Scheduler::constraintSurfaceLayout(
|
||||
SystraceSection s("Scheduler::constraintSurfaceLayout");
|
||||
|
||||
shadowTreeRegistry_.visit(surfaceId, [&](const ShadowTree &shadowTree) {
|
||||
shadowTree.synchronize([&]() {
|
||||
shadowTree.constraintLayout(layoutConstraints, layoutContext);
|
||||
});
|
||||
return shadowTree.constraintLayout(layoutConstraints, layoutContext);
|
||||
});
|
||||
}
|
||||
|
||||
@@ -189,7 +198,13 @@ void Scheduler::uiManagerDidFinishTransaction(
|
||||
SystraceSection s("Scheduler::uiManagerDidFinishTransaction");
|
||||
|
||||
shadowTreeRegistry_.visit(surfaceId, [&](const ShadowTree &shadowTree) {
|
||||
shadowTree.synchronize([&]() { shadowTree.complete(rootChildNodes); });
|
||||
shadowTree.commit(
|
||||
[&](const SharedRootShadowNode &oldRootShadowNode) {
|
||||
return std::make_shared<RootShadowNode>(
|
||||
*oldRootShadowNode,
|
||||
ShadowNodeFragment{.children = rootChildNodes});
|
||||
},
|
||||
std::numeric_limits<int>::max());
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
@@ -38,7 +38,12 @@ ShadowTree::ShadowTree(
|
||||
}
|
||||
|
||||
ShadowTree::~ShadowTree() {
|
||||
complete(std::make_shared<SharedShadowNodeList>(SharedShadowNodeList{}));
|
||||
commit([](const SharedRootShadowNode &oldRootShadowNode) {
|
||||
return std::make_shared<RootShadowNode>(
|
||||
*oldRootShadowNode,
|
||||
ShadowNodeFragment{.children =
|
||||
ShadowNode::emptySharedShadowNodeSharedList()});
|
||||
});
|
||||
}
|
||||
|
||||
Tag ShadowTree::getSurfaceId() const {
|
||||
@@ -46,15 +51,10 @@ Tag ShadowTree::getSurfaceId() const {
|
||||
}
|
||||
|
||||
SharedRootShadowNode ShadowTree::getRootShadowNode() const {
|
||||
std::lock_guard<std::recursive_mutex> lock(commitMutex_);
|
||||
std::shared_lock<folly::SharedMutex> lock(commitMutex_);
|
||||
return rootShadowNode_;
|
||||
}
|
||||
|
||||
void ShadowTree::synchronize(std::function<void(void)> function) const {
|
||||
std::lock_guard<std::recursive_mutex> lock(commitMutex_);
|
||||
function();
|
||||
}
|
||||
|
||||
#pragma mark - Layout
|
||||
|
||||
Size ShadowTree::measure(
|
||||
@@ -69,14 +69,12 @@ Size ShadowTree::measure(
|
||||
bool ShadowTree::constraintLayout(
|
||||
const LayoutConstraints &layoutConstraints,
|
||||
const LayoutContext &layoutContext) const {
|
||||
auto oldRootShadowNode = getRootShadowNode();
|
||||
auto newRootShadowNode =
|
||||
cloneRootShadowNode(oldRootShadowNode, layoutConstraints, layoutContext);
|
||||
return complete(oldRootShadowNode, newRootShadowNode);
|
||||
return commit([&](const SharedRootShadowNode &oldRootShadowNode) {
|
||||
return cloneRootShadowNode(
|
||||
oldRootShadowNode, layoutConstraints, layoutContext);
|
||||
});
|
||||
}
|
||||
|
||||
#pragma mark - Commiting
|
||||
|
||||
UnsharedRootShadowNode ShadowTree::cloneRootShadowNode(
|
||||
const SharedRootShadowNode &oldRootShadowNode,
|
||||
const LayoutConstraints &layoutConstraints,
|
||||
@@ -88,85 +86,98 @@ UnsharedRootShadowNode ShadowTree::cloneRootShadowNode(
|
||||
return newRootShadowNode;
|
||||
}
|
||||
|
||||
bool ShadowTree::complete(
|
||||
const SharedShadowNodeUnsharedList &rootChildNodes) const {
|
||||
auto oldRootShadowNode = getRootShadowNode();
|
||||
auto newRootShadowNode = std::make_shared<RootShadowNode>(
|
||||
*oldRootShadowNode,
|
||||
ShadowNodeFragment{.children =
|
||||
SharedShadowNodeSharedList(rootChildNodes)});
|
||||
|
||||
return complete(oldRootShadowNode, newRootShadowNode);
|
||||
}
|
||||
|
||||
bool ShadowTree::completeByReplacingShadowNode(
|
||||
const SharedShadowNode &oldShadowNode,
|
||||
const SharedShadowNode &newShadowNode) const {
|
||||
auto rootShadowNode = getRootShadowNode();
|
||||
std::vector<std::reference_wrapper<const ShadowNode>> ancestors;
|
||||
oldShadowNode->constructAncestorPath(*rootShadowNode, ancestors);
|
||||
return commit([&](const SharedRootShadowNode &oldRootShadowNode) {
|
||||
std::vector<std::reference_wrapper<const ShadowNode>> ancestors;
|
||||
oldShadowNode->constructAncestorPath(*oldRootShadowNode, ancestors);
|
||||
|
||||
if (ancestors.size() == 0) {
|
||||
return false;
|
||||
}
|
||||
if (ancestors.size() == 0) {
|
||||
return UnsharedRootShadowNode{nullptr};
|
||||
}
|
||||
|
||||
auto oldChild = oldShadowNode;
|
||||
auto newChild = newShadowNode;
|
||||
auto oldChild = oldShadowNode;
|
||||
auto newChild = newShadowNode;
|
||||
|
||||
SharedShadowNodeUnsharedList sharedChildren;
|
||||
SharedShadowNodeUnsharedList sharedChildren;
|
||||
|
||||
for (const auto &ancestor : ancestors) {
|
||||
auto children = ancestor.get().getChildren();
|
||||
std::replace(children.begin(), children.end(), oldChild, newChild);
|
||||
for (const auto &ancestor : ancestors) {
|
||||
auto children = ancestor.get().getChildren();
|
||||
std::replace(children.begin(), children.end(), oldChild, newChild);
|
||||
|
||||
sharedChildren = std::make_shared<SharedShadowNodeList>(children);
|
||||
sharedChildren = std::make_shared<SharedShadowNodeList>(children);
|
||||
|
||||
oldChild = ancestor.get().shared_from_this();
|
||||
newChild = oldChild->clone(ShadowNodeFragment{.children = sharedChildren});
|
||||
}
|
||||
oldChild = ancestor.get().shared_from_this();
|
||||
newChild =
|
||||
oldChild->clone(ShadowNodeFragment{.children = sharedChildren});
|
||||
}
|
||||
|
||||
return complete(sharedChildren);
|
||||
}
|
||||
|
||||
bool ShadowTree::complete(
|
||||
const SharedRootShadowNode &oldRootShadowNode,
|
||||
const UnsharedRootShadowNode &newRootShadowNode) const {
|
||||
SystraceSection s("ShadowTree::complete");
|
||||
newRootShadowNode->layout();
|
||||
newRootShadowNode->sealRecursive();
|
||||
|
||||
auto mutations =
|
||||
calculateShadowViewMutations(*oldRootShadowNode, *newRootShadowNode);
|
||||
|
||||
if (!commit(oldRootShadowNode, newRootShadowNode, mutations)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
emitLayoutEvents(mutations);
|
||||
|
||||
if (delegate_) {
|
||||
delegate_->shadowTreeDidCommit(*this, mutations);
|
||||
}
|
||||
|
||||
return true;
|
||||
return std::make_shared<RootShadowNode>(
|
||||
*oldRootShadowNode, ShadowNodeFragment{.children = sharedChildren});
|
||||
});
|
||||
}
|
||||
|
||||
bool ShadowTree::commit(
|
||||
const SharedRootShadowNode &oldRootShadowNode,
|
||||
const SharedRootShadowNode &newRootShadowNode,
|
||||
const ShadowViewMutationList &mutations) const {
|
||||
std::function<UnsharedRootShadowNode(
|
||||
const SharedRootShadowNode &oldRootShadowNode)> transaction,
|
||||
int attempts,
|
||||
int *revision) const {
|
||||
SystraceSection s("ShadowTree::commit");
|
||||
std::lock_guard<std::recursive_mutex> lock(commitMutex_);
|
||||
|
||||
if (oldRootShadowNode != rootShadowNode_) {
|
||||
return false;
|
||||
while (attempts) {
|
||||
attempts--;
|
||||
|
||||
SharedRootShadowNode oldRootShadowNode;
|
||||
|
||||
{
|
||||
// Reading `rootShadowNode_` in shared manner.
|
||||
std::shared_lock<folly::SharedMutex> lock(commitMutex_);
|
||||
oldRootShadowNode = rootShadowNode_;
|
||||
}
|
||||
|
||||
UnsharedRootShadowNode newRootShadowNode = transaction(oldRootShadowNode);
|
||||
|
||||
if (!newRootShadowNode) {
|
||||
break;
|
||||
}
|
||||
|
||||
newRootShadowNode->layout();
|
||||
newRootShadowNode->sealRecursive();
|
||||
|
||||
auto mutations =
|
||||
calculateShadowViewMutations(*oldRootShadowNode, *newRootShadowNode);
|
||||
|
||||
{
|
||||
// Updating `rootShadowNode_` in unique manner if it hasn't changed.
|
||||
std::unique_lock<folly::SharedMutex> lock(commitMutex_);
|
||||
|
||||
if (rootShadowNode_ != oldRootShadowNode) {
|
||||
continue;
|
||||
}
|
||||
|
||||
rootShadowNode_ = newRootShadowNode;
|
||||
|
||||
toggleEventEmitters(mutations);
|
||||
|
||||
revision_++;
|
||||
|
||||
// Returning last revision if requested.
|
||||
if (revision) {
|
||||
*revision = revision_;
|
||||
}
|
||||
}
|
||||
|
||||
emitLayoutEvents(mutations);
|
||||
|
||||
if (delegate_) {
|
||||
delegate_->shadowTreeDidCommit(*this, mutations);
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
rootShadowNode_ = newRootShadowNode;
|
||||
|
||||
toggleEventEmitters(mutations);
|
||||
|
||||
return true;
|
||||
return false;
|
||||
}
|
||||
|
||||
void ShadowTree::emitLayoutEvents(
|
||||
|
||||
@@ -5,8 +5,9 @@
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <folly/SharedMutex.h>
|
||||
#include <memory>
|
||||
#include <mutex>
|
||||
#include <shared_mutex>
|
||||
|
||||
#include <react/components/root/RootShadowNode.h>
|
||||
#include <react/core/LayoutConstraints.h>
|
||||
@@ -38,16 +39,6 @@ class ShadowTree final {
|
||||
*/
|
||||
SurfaceId getSurfaceId() const;
|
||||
|
||||
/*
|
||||
* Synchronously runs `function` when `commitMutex_` is acquired.
|
||||
* It is useful in cases when transactional consistency and/or successful
|
||||
* commit are required. E.g. you might want to run `measure` and
|
||||
* `constraintLayout` as part of a single congious transaction.
|
||||
* Use this only if it is necessary. All public methods of the class are
|
||||
* already thread-safe.
|
||||
*/
|
||||
void synchronize(std::function<void(void)> function) const;
|
||||
|
||||
#pragma mark - Layout
|
||||
|
||||
/*
|
||||
@@ -71,11 +62,19 @@ class ShadowTree final {
|
||||
#pragma mark - Application
|
||||
|
||||
/*
|
||||
* Create a new shadow tree with given `rootChildNodes` and commit.
|
||||
* Can be called from any thread.
|
||||
* Performs commit calling `transaction` function with a `oldRootShadowNode`
|
||||
* and expecting a `newRootShadowNode` as a return value.
|
||||
* The `transaction` function can abort commit returning `nullptr`.
|
||||
* If a `revision` pointer is not null, the method will store there a
|
||||
* contiguous revision number of the successfully performed transaction.
|
||||
* Specify `attempts` to allow performing multiple tries.
|
||||
* Returns `true` if the operation finished successfully.
|
||||
*/
|
||||
bool complete(const SharedShadowNodeUnsharedList &rootChildNodes) const;
|
||||
bool commit(
|
||||
std::function<UnsharedRootShadowNode(
|
||||
const SharedRootShadowNode &oldRootShadowNode)> transaction,
|
||||
int attempts = 1,
|
||||
int *revision = nullptr) const;
|
||||
|
||||
/*
|
||||
* Replaces a given old shadow node with a new one in the tree by cloning all
|
||||
@@ -108,22 +107,14 @@ class ShadowTree final {
|
||||
const LayoutConstraints &layoutConstraints,
|
||||
const LayoutContext &layoutContext) const;
|
||||
|
||||
bool complete(
|
||||
const SharedRootShadowNode &oldRootShadowNode,
|
||||
const UnsharedRootShadowNode &newRootShadowNode) const;
|
||||
|
||||
bool commit(
|
||||
const SharedRootShadowNode &oldRootShadowNode,
|
||||
const SharedRootShadowNode &newRootShadowNode,
|
||||
const ShadowViewMutationList &mutations) const;
|
||||
|
||||
void toggleEventEmitters(const ShadowViewMutationList &mutations) const;
|
||||
void emitLayoutEvents(const ShadowViewMutationList &mutations) const;
|
||||
|
||||
const SurfaceId surfaceId_;
|
||||
mutable folly::SharedMutex commitMutex_;
|
||||
mutable SharedRootShadowNode rootShadowNode_; // Protected by `commitMutex_`.
|
||||
mutable int revision_{1}; // Protected by `commitMutex_`.
|
||||
ShadowTreeDelegate const *delegate_;
|
||||
mutable std::recursive_mutex commitMutex_;
|
||||
};
|
||||
|
||||
} // namespace react
|
||||
|
||||
Reference in New Issue
Block a user