Skip to content

Commit 4a31469

Browse files
[repro] Pull model: the stop transaction is not pulled, and the legacy stop deadlocks
With enableMountingCoordinatorPullModelAndroid, FabricUIManagerBinding::pullAndExecuteTransaction finds the surface's MountingCoordinator in the binding's surface registry. A surface is not in the registry while it stops: - The transaction that stops a bridgeless surface is never pulled. native_stopSurfaceWithSurfaceHandler_pullModel_pullsDeletes records no Deletes. - The legacy stopSurface(int) holds the registry lock across SurfaceHandler::stop(), and on the UI thread the stop commit pulls inline and blocks on the same lock. native_stopSurface_pullModel_pullsDeletes hangs. native_stopSurfaceWithSurfaceHandler_pushModel_pullsDeletes is a control: push pulls the stop transaction in the stop commit. The tests drive a real FabricUIManagerBinding through FabricMountingManagerTestHelper, which runs no JavaScript and commits views itself.
1 parent bab8ccd commit 4a31469

4 files changed

Lines changed: 334 additions & 3 deletions

File tree

‎packages/react-native/ReactAndroid/src/main/jni/react/fabric/test_helper/FabricMountingManagerTestHelper.cpp‎

Lines changed: 136 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,14 +7,54 @@
77

88
#include "FabricMountingManagerTestHelper.h"
99

10+
#include <react/fabric/FabricUIManagerBinding.h>
1011
#include <react/renderer/animationbackend/AnimatedProps.h>
12+
#include <react/renderer/componentregistry/ComponentDescriptorProviderRegistry.h>
13+
#include <react/renderer/components/root/RootShadowNode.h>
1114
#include <react/renderer/components/view/ViewComponentDescriptor.h>
1215
#include <react/renderer/components/view/ViewProps.h>
1316
#include <react/renderer/core/ComponentDescriptor.h>
17+
#include <react/renderer/mounting/MountingOverrideDelegate.h>
1418
#include <react/renderer/mounting/ShadowView.h>
19+
#include <react/renderer/scheduler/Scheduler.h>
1520

1621
namespace facebook::react {
1722

23+
class FabricMountingManagerTestHelper::DeleteRecorder
24+
: public MountingOverrideDelegate {
25+
public:
26+
bool shouldOverridePullTransaction() const override {
27+
return true;
28+
}
29+
30+
std::optional<MountingTransaction> pullTransaction(
31+
SurfaceId surfaceId,
32+
MountingTransaction::Number number,
33+
const TransactionTelemetry& telemetry,
34+
ShadowViewMutationList mutations) const override {
35+
for (const auto& mutation : mutations) {
36+
if (mutation.type == ShadowViewMutation::Delete) {
37+
deletedTags.push_back(mutation.oldChildShadowView.tag);
38+
}
39+
}
40+
return MountingTransaction{
41+
surfaceId, number, std::move(mutations), telemetry};
42+
}
43+
44+
mutable std::vector<Tag> deletedTags;
45+
};
46+
47+
namespace {
48+
49+
void runNoJavaScript(std::function<void(jsi::Runtime& runtime)>&& /*unused*/) {}
50+
51+
std::vector<Tag> toTags(jni::alias_ref<jni::JArrayInt> tags) {
52+
auto region = tags->getRegion(0, static_cast<jsize>(tags->size()));
53+
return {region.get(), region.get() + tags->size()};
54+
}
55+
56+
} // namespace
57+
1858
FabricMountingManagerTestHelper::FabricMountingManagerTestHelper(
1959
jni::alias_ref<JFabricUIManager::javaobject> jFabricUIManager)
2060
: javaUIManager_(jni::make_global(jFabricUIManager)) {
@@ -81,6 +121,85 @@ void FabricMountingManagerTestHelper::synchronouslyUpdateAnimatedProps(
81121
mountingManager_->synchronouslyUpdateAnimatedProps(updates);
82122
}
83123

124+
jni::local_ref<JRuntimeExecutor::jhybridobject>
125+
FabricMountingManagerTestHelper::createRuntimeExecutor() {
126+
return JRuntimeExecutor::newObjectCxxArgs(runNoJavaScript);
127+
}
128+
129+
jni::local_ref<JRuntimeScheduler::jhybridobject>
130+
FabricMountingManagerTestHelper::createRuntimeScheduler() {
131+
// Work scheduled here never runs and stays queued. A queued task holds the
132+
// UIManager, which holds this scheduler, so both outlive the test.
133+
runtimeScheduler_ = std::make_shared<RuntimeScheduler>(runNoJavaScript);
134+
return JRuntimeScheduler::newObjectCxxArgs(runtimeScheduler_);
135+
}
136+
137+
void FabricMountingManagerTestHelper::registerViewComponent(
138+
ComponentFactory* componentFactory) {
139+
componentFactory->buildRegistryFunction =
140+
[](const EventDispatcher::Weak& eventDispatcher,
141+
const std::shared_ptr<const ContextContainer>& contextContainer) {
142+
auto providerRegistry =
143+
std::make_shared<ComponentDescriptorProviderRegistry>();
144+
providerRegistry->add(
145+
concreteComponentDescriptorProvider<ViewComponentDescriptor>());
146+
return providerRegistry->createComponentDescriptorRegistry(
147+
{.eventDispatcher = eventDispatcher,
148+
.contextContainer = contextContainer,
149+
.flavor = nullptr});
150+
};
151+
}
152+
153+
void FabricMountingManagerTestHelper::commitViews(
154+
jint surfaceId,
155+
jni::alias_ref<jni::JArrayInt> tags) {
156+
auto children =
157+
std::make_shared<std::vector<std::shared_ptr<const ShadowNode>>>();
158+
for (auto tag : toTags(tags)) {
159+
auto props = std::make_shared<ViewProps>();
160+
props->collapsable = false;
161+
children->push_back(viewComponentDescriptor_->createShadowNode(
162+
{.props = props},
163+
viewComponentDescriptor_->createFamily(
164+
{.tag = tag, .surfaceId = surfaceId, .instanceHandle = nullptr})));
165+
}
166+
javaUIManager_->getBinding()
167+
->getScheduler()
168+
->getUIManager()
169+
->getShadowTreeRegistry()
170+
.visit(surfaceId, [&](const ShadowTree& shadowTree) {
171+
shadowTree.commit(
172+
[&](const RootShadowNode& oldRootShadowNode) {
173+
return std::make_shared<RootShadowNode>(
174+
oldRootShadowNode,
175+
ShadowNodeFragment{
176+
.props = ShadowNodeFragment::propsPlaceholder(),
177+
.children = children});
178+
},
179+
{});
180+
});
181+
}
182+
183+
void FabricMountingManagerTestHelper::recordPulledDeletes(jint surfaceId) {
184+
deleteRecorder_ = std::make_shared<DeleteRecorder>();
185+
javaUIManager_->getBinding()
186+
->getScheduler()
187+
->getUIManager()
188+
->getShadowTreeRegistry()
189+
.visit(surfaceId, [&](const ShadowTree& shadowTree) {
190+
shadowTree.getMountingCoordinator()->setMountingOverrideDelegate(
191+
deleteRecorder_);
192+
});
193+
}
194+
195+
jni::local_ref<jni::JArrayInt>
196+
FabricMountingManagerTestHelper::pulledDeleteTags() {
197+
const auto& tags = deleteRecorder_->deletedTags;
198+
auto array = jni::JArrayInt::newArray(tags.size());
199+
array->setRegion(0, static_cast<jsize>(tags.size()), tags.data());
200+
return array;
201+
}
202+
84203
void FabricMountingManagerTestHelper::registerNatives() {
85204
registerHybrid({
86205
makeNativeMethod(
@@ -99,6 +218,23 @@ void FabricMountingManagerTestHelper::registerNatives() {
99218
makeNativeMethod(
100219
"synchronouslyUpdateAnimatedProps",
101220
FabricMountingManagerTestHelper::synchronouslyUpdateAnimatedProps),
221+
makeNativeMethod(
222+
"createRuntimeExecutor",
223+
FabricMountingManagerTestHelper::createRuntimeExecutor),
224+
makeNativeMethod(
225+
"createRuntimeScheduler",
226+
FabricMountingManagerTestHelper::createRuntimeScheduler),
227+
makeNativeMethod(
228+
"registerViewComponent",
229+
FabricMountingManagerTestHelper::registerViewComponent),
230+
makeNativeMethod(
231+
"commitViews", FabricMountingManagerTestHelper::commitViews),
232+
makeNativeMethod(
233+
"recordPulledDeletes",
234+
FabricMountingManagerTestHelper::recordPulledDeletes),
235+
makeNativeMethod(
236+
"pulledDeleteTags",
237+
FabricMountingManagerTestHelper::pulledDeleteTags),
102238
});
103239
}
104240

‎packages/react-native/ReactAndroid/src/main/jni/react/fabric/test_helper/FabricMountingManagerTestHelper.h‎

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,9 +8,12 @@
88
#pragma once
99

1010
#include <fbjni/fbjni.h>
11+
#include <react/jni/JRuntimeExecutor.h>
12+
#include <react/jni/JRuntimeScheduler.h>
1113
#include <react/jni/NativeMap.h>
1214
#include <react/renderer/components/view/ViewComponentDescriptor.h>
1315

16+
#include <react/fabric/ComponentFactory.h>
1417
#include <react/fabric/FabricMountingManager.h>
1518

1619
namespace facebook::react {
@@ -31,6 +34,17 @@ class FabricMountingManagerTestHelper : public jni::HybridClass<FabricMountingMa
3134
jni::alias_ref<jni::JArrayInt> tags,
3235
jni::alias_ref<jni::JArrayClass<NativeMap::javaobject>> props);
3336

37+
// A FabricUIManagerBinding registered on the helper's FabricUIManager with
38+
// these never runs JavaScript: its surfaces mount only what the helper
39+
// commits.
40+
jni::local_ref<JRuntimeExecutor::jhybridobject> createRuntimeExecutor();
41+
jni::local_ref<JRuntimeScheduler::jhybridobject> createRuntimeScheduler();
42+
void registerViewComponent(ComponentFactory *componentFactory);
43+
44+
void commitViews(jint surfaceId, jni::alias_ref<jni::JArrayInt> tags);
45+
void recordPulledDeletes(jint surfaceId);
46+
jni::local_ref<jni::JArrayInt> pulledDeleteTags();
47+
3448
static void registerNatives();
3549

3650
private:
@@ -45,6 +59,9 @@ class FabricMountingManagerTestHelper : public jni::HybridClass<FabricMountingMa
4559
jni::global_ref<JFabricUIManager::javaobject> javaUIManager_;
4660
std::shared_ptr<FabricMountingManager> mountingManager_;
4761
std::unique_ptr<ViewComponentDescriptor> viewComponentDescriptor_;
62+
std::shared_ptr<RuntimeScheduler> runtimeScheduler_;
63+
class DeleteRecorder;
64+
std::shared_ptr<DeleteRecorder> deleteRecorder_;
4865
};
4966

5067
} // namespace facebook::react

0 commit comments

Comments
 (0)