mirror of
https://github.com/facebook/react-native.git
synced 2025-11-01 09:14:26 +00:00
Summary: I spent the last several days thinking about state reconciliation issues, some crashes (T65586949) that suspiciously happen somewhere inside, and a bunch of issues that might be connected to that (possibly, some of T65516263 sub-task). I cannot see some obvious problems in the current state reconciliation algorithm that might cause the crash (because of some use-after-free or other pure C++ issues), but I suspect some of the problems we experience might be caused by some details of how we reconcile states. In the current approach, we rank all states based on the "hierarchical" history of their creation (state version is being calculated based on the version of the base tree). That's usually fine but in some cases when trees are being constructed concurrently, a logical version of a based tree does not correspond to the local version of a committed tree. In other words, the linear history of commits does not always correspond to the "hierarchical" history of trees generation that was done by different parties (e.g. React vs native state update pipeline). In this diff, I tried to change the approach to change the algorithm to follow this logic: If some state is `obsolete` (already been committed and then replaced with newer one), we replace that with the most recent one. This change does not introduce the `obsolete` flag; is already used by State infra to avoid cloning nodes with an outdated state. Interestingly, it fixes the issue with an empty BottomSheet on Android (T66177144). See the attached video. The hope is that it's also will This change theoretically might affect all things that use State, so it hard to predict what can break and how. So, if we don't see obvious problems here, I would set up a GK/QE and run the experiment in prod. Changelog: [Internal] Fabric-specific internal change. Reviewed By: JoshuaGross Differential Revision: D21295137 fbshipit-source-id: e5613218d3e11a56623cab9bbf2540495b2b24e8
144 lines
3.7 KiB
C++
144 lines
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.
|
|
*/
|
|
|
|
#include "ShadowNodeFamily.h"
|
|
#include "ShadowNode.h"
|
|
|
|
#include <react/core/ComponentDescriptor.h>
|
|
#include <react/core/State.h>
|
|
|
|
namespace facebook {
|
|
namespace react {
|
|
|
|
using AncestorList = ShadowNode::AncestorList;
|
|
|
|
ShadowNodeFamily::ShadowNodeFamily(
|
|
ShadowNodeFamilyFragment const &fragment,
|
|
EventDispatcher::Weak eventDispatcher,
|
|
ComponentDescriptor const &componentDescriptor)
|
|
: eventDispatcher_(eventDispatcher),
|
|
tag_(fragment.tag),
|
|
surfaceId_(fragment.surfaceId),
|
|
eventEmitter_(fragment.eventEmitter),
|
|
componentDescriptor_(componentDescriptor),
|
|
componentHandle_(componentDescriptor.getComponentHandle()),
|
|
componentName_(componentDescriptor.getComponentName()) {}
|
|
|
|
void ShadowNodeFamily::setParent(ShadowNodeFamily::Shared const &parent) const {
|
|
assert(parent_.lock() == nullptr || parent_.lock() == parent);
|
|
if (hasParent_) {
|
|
return;
|
|
}
|
|
|
|
parent_ = parent;
|
|
hasParent_ = true;
|
|
}
|
|
|
|
ComponentHandle ShadowNodeFamily::getComponentHandle() const {
|
|
return componentHandle_;
|
|
}
|
|
|
|
SurfaceId ShadowNodeFamily::getSurfaceId() const {
|
|
return surfaceId_;
|
|
}
|
|
|
|
ComponentName ShadowNodeFamily::getComponentName() const {
|
|
return componentName_;
|
|
}
|
|
|
|
const ComponentDescriptor &ShadowNodeFamily::getComponentDescriptor() const {
|
|
return componentDescriptor_;
|
|
}
|
|
|
|
AncestorList ShadowNodeFamily::getAncestors(
|
|
ShadowNode const &ancestorShadowNode) const {
|
|
auto families = better::small_vector<ShadowNodeFamily const *, 64>{};
|
|
auto ancestorFamily = ancestorShadowNode.family_.get();
|
|
|
|
auto family = this;
|
|
while (family && family != ancestorFamily) {
|
|
families.push_back(family);
|
|
family = family->parent_.lock().get();
|
|
}
|
|
|
|
if (family != ancestorFamily) {
|
|
return {};
|
|
}
|
|
|
|
auto ancestors = AncestorList{};
|
|
auto parentNode = &ancestorShadowNode;
|
|
for (auto it = families.rbegin(); it != families.rend(); it++) {
|
|
auto childFamily = *it;
|
|
auto found = false;
|
|
auto childIndex = 0;
|
|
for (const auto &childNode : *parentNode->children_) {
|
|
if (childNode->family_.get() == childFamily) {
|
|
ancestors.push_back({*parentNode, childIndex});
|
|
parentNode = childNode.get();
|
|
found = true;
|
|
break;
|
|
}
|
|
childIndex++;
|
|
}
|
|
|
|
if (!found) {
|
|
ancestors.clear();
|
|
return ancestors;
|
|
}
|
|
}
|
|
|
|
return ancestors;
|
|
}
|
|
|
|
State::Shared ShadowNodeFamily::getMostRecentState() const {
|
|
std::unique_lock<better::shared_mutex> lock(mutex_);
|
|
return mostRecentState_;
|
|
}
|
|
|
|
void ShadowNodeFamily::setMostRecentState(State::Shared const &state) const {
|
|
std::unique_lock<better::shared_mutex> lock(mutex_);
|
|
|
|
/*
|
|
* Checking and setting `isObsolete_` prevents old states to be recommitted
|
|
* on top of fresher states. It's okay to commit a tree with "older" Shadow
|
|
* Nodes (the evolution of nodes is not linear), however, we never back out
|
|
* states (they progress linearly).
|
|
*/
|
|
if (state && state->isObsolete_) {
|
|
return;
|
|
}
|
|
|
|
if (mostRecentState_) {
|
|
mostRecentState_->isObsolete_ = true;
|
|
}
|
|
|
|
mostRecentState_ = state;
|
|
}
|
|
|
|
std::shared_ptr<State const> ShadowNodeFamily::getMostRecentStateIfObsolete(
|
|
State const &state) const {
|
|
std::unique_lock<better::shared_mutex> lock(mutex_);
|
|
if (!state.isObsolete_) {
|
|
return {};
|
|
}
|
|
return mostRecentState_;
|
|
}
|
|
|
|
void ShadowNodeFamily::dispatchRawState(
|
|
StateUpdate &&stateUpdate,
|
|
EventPriority priority) const {
|
|
auto eventDispatcher = eventDispatcher_.lock();
|
|
if (!eventDispatcher) {
|
|
return;
|
|
}
|
|
|
|
eventDispatcher->dispatchStateUpdate(std::move(stateUpdate), priority);
|
|
}
|
|
|
|
} // namespace react
|
|
} // namespace facebook
|