Skip to content

Commit

Permalink
Fabric: New non-blocking treading model for ShadowTree
Browse files Browse the repository at this point in the history
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
  • Loading branch information
shergin authored and facebook-github-bot committed Jan 17, 2019
1 parent 8f9ca2b commit 5a58ca4
Show file tree
Hide file tree
Showing 3 changed files with 119 additions and 102 deletions.
31 changes: 23 additions & 8 deletions ReactCommon/fabric/uimanager/Scheduler.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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 "
Expand All @@ -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);
Expand Down Expand Up @@ -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);
});
}

Expand Down Expand Up @@ -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());
});
}

Expand Down
151 changes: 81 additions & 70 deletions ReactCommon/fabric/uimanager/ShadowTree.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -38,23 +38,23 @@ 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 {
return surfaceId_;
}

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(
Expand All @@ -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,
Expand All @@ -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);
return std::make_shared<RootShadowNode>(
*oldRootShadowNode, ShadowNodeFragment{.children = sharedChildren});
});
}

bool ShadowTree::complete(
const SharedRootShadowNode &oldRootShadowNode,
const UnsharedRootShadowNode &newRootShadowNode) const {
SystraceSection s("ShadowTree::complete");
newRootShadowNode->layout();
newRootShadowNode->sealRecursive();
bool ShadowTree::commit(
std::function<UnsharedRootShadowNode(
const SharedRootShadowNode &oldRootShadowNode)> transaction,
int attempts,
int *revision) const {
SystraceSection s("ShadowTree::commit");

auto mutations =
calculateShadowViewMutations(*oldRootShadowNode, *newRootShadowNode);
while (attempts) {
attempts--;

if (!commit(oldRootShadowNode, newRootShadowNode, mutations)) {
return false;
}
SharedRootShadowNode oldRootShadowNode;

emitLayoutEvents(mutations);
{
// Reading `rootShadowNode_` in shared manner.
std::shared_lock<folly::SharedMutex> lock(commitMutex_);
oldRootShadowNode = rootShadowNode_;
}

if (delegate_) {
delegate_->shadowTreeDidCommit(*this, mutations);
}
UnsharedRootShadowNode newRootShadowNode = transaction(oldRootShadowNode);

return true;
}
if (!newRootShadowNode) {
break;
}

bool ShadowTree::commit(
const SharedRootShadowNode &oldRootShadowNode,
const SharedRootShadowNode &newRootShadowNode,
const ShadowViewMutationList &mutations) const {
SystraceSection s("ShadowTree::commit");
std::lock_guard<std::recursive_mutex> lock(commitMutex_);
newRootShadowNode->layout();
newRootShadowNode->sealRecursive();

if (oldRootShadowNode != rootShadowNode_) {
return false;
}
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;
rootShadowNode_ = newRootShadowNode;

toggleEventEmitters(mutations);
toggleEventEmitters(mutations);

revision_++;

// Returning last revision if requested.
if (revision) {
*revision = revision_;
}
}

emitLayoutEvents(mutations);

if (delegate_) {
delegate_->shadowTreeDidCommit(*this, mutations);
}

return true;
}

return true;
return false;
}

void ShadowTree::emitLayoutEvents(
Expand Down
39 changes: 15 additions & 24 deletions ReactCommon/fabric/uimanager/ShadowTree.h
Original file line number Diff line number Diff line change
Expand Up @@ -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>
Expand Down Expand Up @@ -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

/*
Expand All @@ -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
Expand Down Expand Up @@ -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
Expand Down

0 comments on commit 5a58ca4

Please sign in to comment.