David Vacca 79cc1468bc Fix race condition in Fabric Scheduler
Summary:
This diff fixes a race condition that was detected on "Marketplace You" production test for Android.
The race condition happens when the method ShadowTree::complete is executed concurrently from two threads (in this case this is called from Scheduller::constraintSurfaceLayout and Scheduler::uiManagerDidFinishTransaction), based on the order of execution this bug makes MountViewItems to be dispatched to the UI in the wrong order.

The root cause of the bug is in the method:

```
bool ShadowTree::complete(
    const SharedRootShadowNode &oldRootShadowNode,
    const UnsharedRootShadowNode &newRootShadowNode) const {
  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;
}
```
Notes:
- the commit method is guarded by the commitMutex_
- the shadowTreeDidCommit() method dispatches mutations instructions to the platform side.
- If there are two threads running concurrently, there is no guarantee that the shadowTreeDidCommit() is going to be called in the correct order.

The solution is to include the execution to shadowTreeDidCommit() in the same commitMutex_

Possible solutions:
1 - move the commitMutex_ out of the commit method (to the completeMethod)
2 - synchronize the call to complete method() - this is the implemented solution.
I chose this solution to make it consistent with the way Scheduler::constraintSurfaceLayout is implemented (https://fburl.com/8l49no5x)

This mechanism is very likely to change in the refactor of threading mechanism that Valentin Shergin is going to be working on January.

I would like to land this, so we can fix this bug and run another experiment in production as soon as possible.

Reviewed By: sahrens

Differential Revision: D13535587

fbshipit-source-id: bedd4d85f5569ab3733c302d1328aa48017bcaad
2018-12-26 11:15:34 -08:00

198 lines
6.0 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 "Scheduler.h"
#include <jsi/jsi.h>
#include <react/core/LayoutContext.h>
#include <react/uimanager/ComponentDescriptorRegistry.h>
#include <react/uimanager/UIManager.h>
#include <react/uimanager/UIManagerBinding.h>
#include <react/uimanager/UITemplateProcessor.h>
#include "ComponentDescriptorFactory.h"
namespace facebook {
namespace react {
Scheduler::Scheduler(const SharedContextContainer &contextContainer) {
const auto asynchronousEventBeatFactory =
contextContainer->getInstance<EventBeatFactory>("asynchronous");
const auto synchronousEventBeatFactory =
contextContainer->getInstance<EventBeatFactory>("synchronous");
runtimeExecutor_ =
contextContainer->getInstance<RuntimeExecutor>("runtime-executor");
reactNativeConfig_ =
contextContainer->getInstance<std::shared_ptr<const ReactNativeConfig>>(
"ReactNativeConfig");
auto uiManager = std::make_unique<UIManager>();
auto &uiManagerRef = *uiManager;
uiManagerBinding_ = std::make_shared<UIManagerBinding>(std::move(uiManager));
auto eventPipe = [uiManagerBinding = uiManagerBinding_.get()](
jsi::Runtime &runtime,
const EventTarget *eventTarget,
const std::string &type,
const ValueFactory &payloadFactory) {
uiManagerBinding->dispatchEvent(runtime, eventTarget, type, payloadFactory);
};
auto eventDispatcher = std::make_shared<EventDispatcher>(
eventPipe, synchronousEventBeatFactory, asynchronousEventBeatFactory);
componentDescriptorRegistry_ = ComponentDescriptorFactory::buildRegistry(
eventDispatcher, contextContainer);
uiManagerRef.setDelegate(this);
uiManagerRef.setShadowTreeRegistry(&shadowTreeRegistry_);
uiManagerRef.setComponentDescriptorRegistry(componentDescriptorRegistry_);
runtimeExecutor_([=](jsi::Runtime &runtime) {
UIManagerBinding::install(runtime, uiManagerBinding_);
});
}
Scheduler::~Scheduler() {
uiManagerBinding_->invalidate();
}
void Scheduler::startSurface(
SurfaceId surfaceId,
const std::string &moduleName,
const folly::dynamic &initialProps,
const LayoutConstraints &layoutConstraints,
const LayoutContext &layoutContext) const {
auto shadowTree =
std::make_unique<ShadowTree>(surfaceId, layoutConstraints, layoutContext);
shadowTree->setDelegate(this);
shadowTreeRegistry_.add(std::move(shadowTree));
#ifndef ANDROID
runtimeExecutor_([=](jsi::Runtime &runtime) {
uiManagerBinding_->startSurface(
runtime, surfaceId, moduleName, initialProps);
});
#endif
}
void Scheduler::renderTemplateToSurface(
SurfaceId surfaceId,
const std::string &uiTemplate) {
try {
if (uiTemplate.size() == 0) {
return;
}
NativeModuleRegistry nMR;
auto tree = UITemplateProcessor::buildShadowTree(
uiTemplate,
surfaceId,
folly::dynamic::object(),
*componentDescriptorRegistry_,
nMR,
reactNativeConfig_);
shadowTreeRegistry_.visit(surfaceId, [=](const ShadowTree &shadowTree) {
shadowTree.complete(
std::make_shared<SharedShadowNodeList>(SharedShadowNodeList{tree}));
});
} catch (const std::exception &e) {
LOG(ERROR) << " >>>> EXCEPTION <<< rendering uiTemplate in "
<< "Scheduler::renderTemplateToSurface: " << e.what();
}
}
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()));
});
auto shadowTree = shadowTreeRegistry_.remove(surfaceId);
shadowTree->setDelegate(nullptr);
#ifndef ANDROID
runtimeExecutor_([=](jsi::Runtime &runtime) {
uiManagerBinding_->stopSurface(runtime, surfaceId);
});
#endif
}
Size Scheduler::measureSurface(
SurfaceId surfaceId,
const LayoutConstraints &layoutConstraints,
const LayoutContext &layoutContext) const {
Size size;
shadowTreeRegistry_.visit(surfaceId, [&](const ShadowTree &shadowTree) {
size = shadowTree.measure(layoutConstraints, layoutContext);
});
return size;
}
void Scheduler::constraintSurfaceLayout(
SurfaceId surfaceId,
const LayoutConstraints &layoutConstraints,
const LayoutContext &layoutContext) const {
shadowTreeRegistry_.visit(surfaceId, [&](const ShadowTree &shadowTree) {
shadowTree.synchronize([&]() {
shadowTree.constraintLayout(layoutConstraints, layoutContext);
});
});
}
#pragma mark - Delegate
void Scheduler::setDelegate(SchedulerDelegate *delegate) {
delegate_ = delegate;
}
SchedulerDelegate *Scheduler::getDelegate() const {
return delegate_;
}
#pragma mark - ShadowTreeDelegate
void Scheduler::shadowTreeDidCommit(
const ShadowTree &shadowTree,
const ShadowViewMutationList &mutations) const {
if (delegate_) {
delegate_->schedulerDidFinishTransaction(
shadowTree.getSurfaceId(), mutations);
}
}
#pragma mark - UIManagerDelegate
void Scheduler::uiManagerDidFinishTransaction(
SurfaceId surfaceId,
const SharedShadowNodeUnsharedList &rootChildNodes) {
shadowTreeRegistry_.visit(surfaceId, [&](const ShadowTree &shadowTree) {
shadowTree.synchronize([&]() { shadowTree.complete(rootChildNodes); });
});
}
void Scheduler::uiManagerDidCreateShadowNode(
const SharedShadowNode &shadowNode) {
if (delegate_) {
auto layoutableShadowNode =
dynamic_cast<const LayoutableShadowNode *>(shadowNode.get());
auto isLayoutable = layoutableShadowNode != nullptr;
delegate_->schedulerDidRequestPreliminaryViewAllocation(
shadowNode->getRootTag(),
shadowNode->getComponentName(),
isLayoutable,
shadowNode->getComponentHandle());
}
}
} // namespace react
} // namespace facebook