Fix crash in PreAllocation optimization

Summary:
PreAllocation currently always happens at revision 0 (after ShadowNode creation), and all CREATE mutations are triggered for ShadowNodes at revision 1 or higher (since CREATE mutations are generated by the differ, it means that all ShadowNodes have revision 1 or higher when CompleteRoot is called). This means that between PreAllocation and CREATE, we /always/ expect at least one clone.

It is possible for a node to be "non-view-forming" at revision 0, causing view preallocation to be skipped, and "view-forming" at revision 1 (causing the CREATE mutation to be thrown away, since all CREATE mutations of revision 0 or 1 are thrown away). This causes a crash. It is extremely marginal, but there are repros in the wild.

Thus, I'm introducing one new UIManager and Scheduler delegate method that allows the mounting layer to be notified of clones. If a clone from rev 0->1 results in a node going from non-view-forming to view-forming, we can preallocate then, as well.

This resolves this crash, and allows us to keep experimenting safely with this View PreAllocation optimization. I believe all edge-cases are accounted for.

Changelog: [Internal]

Reviewed By: sammy-SC

Differential Revision: D29043426

fbshipit-source-id: dff11d3140ded1cbb02a5518a3aeb52dc812cc50
This commit is contained in:
Joshua Gross
2021-06-10 15:50:14 -07:00
committed by Facebook GitHub Bot
parent 36c0a7dec1
commit f2e0b2f45f
8 changed files with 111 additions and 23 deletions
@@ -1128,13 +1128,9 @@ void Binding::driveCxxAnimations() {
scheduler_->animationTick();
}
void Binding::schedulerDidRequestPreliminaryViewAllocation(
void Binding::preallocateShadowView(
const SurfaceId surfaceId,
const ShadowView &shadowView) {
if (disablePreallocateViews_) {
return;
}
jni::global_ref<jobject> localJavaUIManager = getJavaUIManager();
if (!localJavaUIManager) {
LOG(ERROR)
@@ -1144,11 +1140,6 @@ void Binding::schedulerDidRequestPreliminaryViewAllocation(
bool isLayoutableShadowNode = shadowView.layoutMetrics != EmptyLayoutMetrics;
if (disableVirtualNodePreallocation_ &&
!shadowView.traits.check(ShadowNodeTraits::Trait::FormsView)) {
return;
}
static auto preallocateView =
jni::findClassStatic(Binding::UIManagerJavaDescriptor)
->getMethod<void(
@@ -1191,6 +1182,53 @@ void Binding::schedulerDidRequestPreliminaryViewAllocation(
isLayoutableShadowNode);
}
void Binding::schedulerDidRequestPreliminaryViewAllocation(
const SurfaceId surfaceId,
const ShadowNode &shadowNode) {
if (disablePreallocateViews_) {
return;
}
auto shadowView = ShadowView(shadowNode);
if (disableVirtualNodePreallocation_ &&
!shadowView.traits.check(ShadowNodeTraits::Trait::FormsView)) {
return;
}
preallocateShadowView(surfaceId, shadowView);
}
void Binding::schedulerDidCloneShadowNode(
SurfaceId surfaceId,
const ShadowNode &oldShadowNode,
const ShadowNode &newShadowNode) {
// This is only necessary if view preallocation was skipped during
// createShadowNode
if (!disableVirtualNodePreallocation_) {
return;
}
// We may need to PreAllocate a ShadowNode at this point if this is the
// earliest point it is possible to do so:
// 1. The revision is exactly 1
// 2. At revision 0 (the old node), View Preallocation would have been skipped
if (newShadowNode.getProps()->revision != 1) {
return;
}
if (oldShadowNode.getProps()->revision != 0) {
return;
}
// If the new node is concrete and the old wasn't, we can preallocate
if (!oldShadowNode.getTraits().check(ShadowNodeTraits::Trait::FormsView) &&
newShadowNode.getTraits().check(ShadowNodeTraits::Trait::FormsView)) {
auto shadowView = ShadowView(newShadowNode);
preallocateShadowView(surfaceId, shadowView);
}
}
void Binding::schedulerDidDispatchCommand(
const ShadowView &shadowView,
std::string const &commandName,
@@ -135,9 +135,18 @@ class Binding : public jni::HybridClass<Binding>,
void schedulerDidFinishTransaction(
MountingCoordinator::Shared const &mountingCoordinator) override;
void preallocateShadowView(
const SurfaceId surfaceId,
const ShadowView &shadowView);
void schedulerDidRequestPreliminaryViewAllocation(
const SurfaceId surfaceId,
const ShadowView &shadowView) override;
const ShadowNode &shadowNode) override;
void schedulerDidCloneShadowNode(
SurfaceId surfaceId,
const ShadowNode &oldShadowNode,
const ShadowNode &newShadowNode) override;
void schedulerDidDispatchCommand(
const ShadowView &shadowView,