Skip to content

Commit 0ede810

Browse files
Pull the stop transaction of a surface in the pull model
In the pull model a commit only notifies the UI thread, and pullAndExecuteTransaction pulls only surfaces found in the binding's surface registry. stopSurfaceWithSurfaceHandler removes the surface from the registry before SurfaceHandler::stop() commits its empty tree, so nothing pulled that transaction. Mounting override delegates such as LayoutAnimations and C++ Animated never saw its Deletes. The pull cannot happen after stop(): the shadow tree is destroyed at its end, which revokes the MountingCoordinator. schedulerShouldRenderTransactions now pulls and executes a commit of an unregistered surface on the commit thread, as the push model does with accumulated props. Registered surfaces are pulled on the UI thread as before. That check reads the registry inside the commit, so stopSurface(int) no longer holds the registry lock across stop(): it takes the surface out of the registry first, as stopSurfaceWithSurfaceHandler does. startSurfaceWithSurfaceHandler (bridgeless) now adds the surface to the registry before SurfaceHandler::start(), as startSurface and startSurfaceWithConstraints already do. Otherwise a commit made while the surface starts would be pulled on the commit thread and queued, and the UI thread's pull of the next commit would mount ahead of it. Now only a stopping surface commits outside the registry. So a registered surface may not be running yet, e.g. a restarted surface, which keeps its id, with a pull or mount report left from its previous run. pullAndExecuteTransaction and reportMount now skip a surface that is not running. native_startSurfaceWithSurfaceHandler_pullModel_mountsCommitDuringStartInOrder commits from another thread in an on-surface-start callback while the start waits, then commits again on the UI thread, and checks the mounted order. It also passes without this change; it fails, with the views mounted as [42, 43], if the surface is added to the registry after start().
1 parent 9c216cd commit 0ede810

6 files changed

Lines changed: 127 additions & 55 deletions

File tree

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

Lines changed: 65 additions & 51 deletions
Original file line numberDiff line numberDiff line change
@@ -35,6 +35,33 @@
3535

3636
namespace facebook::react {
3737

38+
namespace {
39+
40+
std::shared_ptr<const MountingCoordinator> getRunningMountingCoordinator(
41+
const std::variant<
42+
SurfaceHandler,
43+
jni::weak_ref<SurfaceHandlerBinding::jhybridobject>>& registryEntry) {
44+
const auto* surfaceHandler = std::get_if<SurfaceHandler>(&registryEntry);
45+
jni::local_ref<SurfaceHandlerBinding::jhybridobject> javaSurfaceHandler;
46+
if (surfaceHandler == nullptr) {
47+
javaSurfaceHandler =
48+
std::get<jni::weak_ref<SurfaceHandlerBinding::jhybridobject>>(
49+
registryEntry)
50+
.lockLocal();
51+
if (!javaSurfaceHandler) {
52+
return nullptr;
53+
}
54+
surfaceHandler = &javaSurfaceHandler->cthis()->getSurfaceHandler();
55+
}
56+
// Callers hold the registry lock, and a surface leaves it before it stops.
57+
if (surfaceHandler->getStatus() != SurfaceHandler::Status::Running) {
58+
return nullptr;
59+
}
60+
return surfaceHandler->getMountingCoordinator();
61+
}
62+
63+
} // namespace
64+
3865
void FabricUIManagerBinding::initHybrid(jni::alias_ref<jhybridobject> jobj) {
3966
setCxxInstance(jobj);
4067
}
@@ -89,22 +116,10 @@ void FabricUIManagerBinding::reportMount(SurfaceId surfaceId) {
89116
std::shared_lock lock(surfaceHandlerRegistryMutex_);
90117
auto iterator = surfaceHandlerRegistry_.find(surfaceId);
91118
if (iterator != surfaceHandlerRegistry_.end()) {
92-
const auto* surfaceHandler =
93-
std::get_if<SurfaceHandler>(&iterator->second);
94-
if (surfaceHandler == nullptr) {
95-
auto javaSurfaceHandler =
96-
std::get<jni::weak_ref<SurfaceHandlerBinding::jhybridobject>>(
97-
iterator->second)
98-
.lockLocal();
99-
if (javaSurfaceHandler) {
100-
surfaceHandler = &javaSurfaceHandler->cthis()->getSurfaceHandler();
101-
}
102-
}
103-
if (surfaceHandler != nullptr) {
104-
auto mountingCoordinator = surfaceHandler->getMountingCoordinator();
105-
if (mountingCoordinator != nullptr) {
106-
mountingCoordinator->didPerformAsyncTransactions();
107-
}
119+
auto mountingCoordinator =
120+
getRunningMountingCoordinator(iterator->second);
121+
if (mountingCoordinator != nullptr) {
122+
mountingCoordinator->didPerformAsyncTransactions();
108123
}
109124
} else {
110125
LOG(ERROR) << "FabricUIManagerBinding::reportMount: Surface with id "
@@ -130,20 +145,7 @@ void FabricUIManagerBinding::pullAndExecuteTransaction(SurfaceId surfaceId) {
130145
if (iterator == surfaceHandlerRegistry_.end()) {
131146
return;
132147
}
133-
const auto* surfaceHandler = std::get_if<SurfaceHandler>(&iterator->second);
134-
jni::local_ref<SurfaceHandlerBinding::jhybridobject> javaSurfaceHandler;
135-
if (surfaceHandler == nullptr) {
136-
javaSurfaceHandler =
137-
std::get<jni::weak_ref<SurfaceHandlerBinding::jhybridobject>>(
138-
iterator->second)
139-
.lockLocal();
140-
if (javaSurfaceHandler) {
141-
surfaceHandler = &javaSurfaceHandler->cthis()->getSurfaceHandler();
142-
}
143-
}
144-
if (surfaceHandler != nullptr) {
145-
mountingCoordinator = surfaceHandler->getMountingCoordinator();
146-
}
148+
mountingCoordinator = getRunningMountingCoordinator(iterator->second);
147149
}
148150

149151
if (mountingCoordinator == nullptr) {
@@ -169,6 +171,11 @@ void FabricUIManagerBinding::pullAndExecuteTransaction(SurfaceId surfaceId) {
169171
}
170172
}
171173

174+
bool FabricUIManagerBinding::isSurfaceRegistered(SurfaceId surfaceId) {
175+
std::shared_lock lock(surfaceHandlerRegistryMutex_);
176+
return surfaceHandlerRegistry_.contains(surfaceId);
177+
}
178+
172179
#pragma mark - Surface management
173180

174181
// Used by bridgeless
@@ -195,6 +202,13 @@ void FabricUIManagerBinding::startSurfaceWithSurfaceHandler(
195202
<< "FabricUIManagerBinding::startSurfaceWithSurfaceHandler: scheduler disappeared";
196203
return;
197204
}
205+
206+
{
207+
std::unique_lock lock(surfaceHandlerRegistryMutex_);
208+
surfaceHandlerRegistry_.emplace(
209+
surfaceId, jni::make_weak(surfaceHandlerBinding));
210+
}
211+
198212
scheduler->registerSurface(surfaceHandler);
199213

200214
auto mountingManager = getMountingManager("startSurfaceWithSurfaceHandler");
@@ -208,12 +222,6 @@ void FabricUIManagerBinding::startSurfaceWithSurfaceHandler(
208222
surfaceHandler.getMountingCoordinator()->setMountingOverrideDelegate(
209223
animationDriver_);
210224
}
211-
212-
{
213-
std::unique_lock lock(surfaceHandlerRegistryMutex_);
214-
surfaceHandlerRegistry_.emplace(
215-
surfaceId, jni::make_weak(surfaceHandlerBinding));
216-
}
217225
}
218226

219227
// Used by non-bridgeless+Fabric
@@ -459,23 +467,25 @@ void FabricUIManagerBinding::stopSurface(jint surfaceId) {
459467
return;
460468
}
461469

470+
// The surface leaves the registry before it stops: the stop commit reads the
471+
// registry on this thread.
472+
decltype(surfaceHandlerRegistry_)::node_type registryEntry;
462473
{
463474
std::unique_lock lock(surfaceHandlerRegistryMutex_);
464-
auto iterator = surfaceHandlerRegistry_.find(surfaceId);
465-
if (iterator == surfaceHandlerRegistry_.end()) {
466-
LOG(ERROR)
467-
<< "FabricUIManagerBinding::stopSurface: Surface with given id is not found";
468-
return;
469-
}
475+
registryEntry = surfaceHandlerRegistry_.extract(surfaceId);
476+
}
477+
if (registryEntry.empty()) {
478+
LOG(ERROR)
479+
<< "FabricUIManagerBinding::stopSurface: Surface with given id is not found";
480+
return;
481+
}
470482

471-
auto* surfaceHandler = std::get_if<SurfaceHandler>(&iterator->second);
472-
if (surfaceHandler != nullptr) {
473-
surfaceHandler->stop();
474-
scheduler->unregisterSurface(*surfaceHandler);
475-
} else {
476-
LOG(ERROR) << "Java-owned SurfaceHandler found in stopSurface";
477-
}
478-
surfaceHandlerRegistry_.erase(iterator);
483+
auto* surfaceHandler = std::get_if<SurfaceHandler>(&registryEntry.mapped());
484+
if (surfaceHandler != nullptr) {
485+
surfaceHandler->stop();
486+
scheduler->unregisterSurface(*surfaceHandler);
487+
} else {
488+
LOG(ERROR) << "Java-owned SurfaceHandler found in stopSurface";
479489
}
480490

481491
auto mountingManager = getMountingManager("stopSurface");
@@ -727,7 +737,11 @@ void FabricUIManagerBinding::schedulerShouldRenderTransactions(
727737
}
728738
}
729739

730-
if (ReactNativeFeatureFlags::enableMountingCoordinatorPullModelAndroid()) {
740+
// The UI thread pulls only a registered surface, so a commit of a surface
741+
// outside the registry, such as the commit that stops it, is pulled below,
742+
// on the commit thread.
743+
if (ReactNativeFeatureFlags::enableMountingCoordinatorPullModelAndroid() &&
744+
isSurfaceRegistered(mountingCoordinator->getSurfaceId())) {
731745
// Pull model: do NOT pull the transaction or build the batch here (on the
732746
// commit thread). Just notify Java that a transaction is available; the UI
733747
// thread calls back into `pullAndExecuteTransaction` at its next mount

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

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -137,6 +137,8 @@ class FabricUIManagerBinding : public jni::HybridClass<FabricUIManagerBinding>,
137137

138138
void pullAndExecuteTransaction(SurfaceId surfaceId);
139139

140+
bool isSurfaceRegistered(SurfaceId surfaceId);
141+
140142
jint findNextFocusableElement(jint parentTag, jint focusedTag, jint direction);
141143

142144
jintArray getRelativeAncestorList(jint rootTag, jint childTag);

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

Lines changed: 29 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,8 @@
1818
#include <react/renderer/mounting/ShadowView.h>
1919
#include <react/renderer/scheduler/Scheduler.h>
2020

21+
#include <thread>
22+
2123
namespace facebook::react {
2224

2325
class FabricMountingManagerTestHelper::DeleteRecorder
@@ -148,18 +150,38 @@ void FabricMountingManagerTestHelper::registerViewComponent(
148150
};
149151
}
150152

153+
void FabricMountingManagerTestHelper::commitViews(
154+
jint surfaceId,
155+
jni::alias_ref<jni::JArrayInt> tags) {
156+
commitViewTags(surfaceId, toTags(tags));
157+
}
158+
159+
void FabricMountingManagerTestHelper::commitViewsOnSurfaceStart(
160+
jni::alias_ref<jni::JArrayInt> tags) {
161+
javaUIManager_->getBinding()
162+
->getScheduler()
163+
->getUIManager()
164+
->addOnSurfaceStartCallback(
165+
[this, tags = toTags(tags)](const ShadowTree& shadowTree) {
166+
std::thread([&] {
167+
jni::ThreadScope::WithClassLoader(
168+
[&] { commitViewTags(shadowTree.getSurfaceId(), tags); });
169+
}).join();
170+
});
171+
}
172+
151173
void FabricMountingManagerTestHelper::stopSurfaceHandler(
152174
jni::alias_ref<SurfaceHandlerBinding::jhybridobject>
153175
surfaceHandlerBinding) {
154176
surfaceHandlerBinding->cthis()->getSurfaceHandler().stop();
155177
}
156178

157-
void FabricMountingManagerTestHelper::commitViews(
158-
jint surfaceId,
159-
jni::alias_ref<jni::JArrayInt> tags) {
179+
void FabricMountingManagerTestHelper::commitViewTags(
180+
SurfaceId surfaceId,
181+
const std::vector<Tag>& tags) {
160182
auto children =
161183
std::make_shared<std::vector<std::shared_ptr<const ShadowNode>>>();
162-
for (auto tag : toTags(tags)) {
184+
for (auto tag : tags) {
163185
auto props = std::make_shared<ViewProps>();
164186
props->collapsable = false;
165187
children->push_back(viewComponentDescriptor_->createShadowNode(
@@ -233,6 +255,9 @@ void FabricMountingManagerTestHelper::registerNatives() {
233255
FabricMountingManagerTestHelper::registerViewComponent),
234256
makeNativeMethod(
235257
"commitViews", FabricMountingManagerTestHelper::commitViews),
258+
makeNativeMethod(
259+
"commitViewsOnSurfaceStart",
260+
FabricMountingManagerTestHelper::commitViewsOnSurfaceStart),
236261
makeNativeMethod(
237262
"stopSurfaceHandler",
238263
FabricMountingManagerTestHelper::stopSurfaceHandler),

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

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -43,6 +43,7 @@ class FabricMountingManagerTestHelper : public jni::HybridClass<FabricMountingMa
4343
void registerViewComponent(ComponentFactory *componentFactory);
4444

4545
void commitViews(jint surfaceId, jni::alias_ref<jni::JArrayInt> tags);
46+
void commitViewsOnSurfaceStart(jni::alias_ref<jni::JArrayInt> tags);
4647
void stopSurfaceHandler(jni::alias_ref<SurfaceHandlerBinding::jhybridobject> surfaceHandlerBinding);
4748
void recordPulledDeletes(jint surfaceId);
4849
jni::local_ref<jni::JArrayInt> pulledDeleteTags();
@@ -58,6 +59,8 @@ class FabricMountingManagerTestHelper : public jni::HybridClass<FabricMountingMa
5859
jni::alias_ref<jhybridobject> jobj,
5960
jni::alias_ref<JFabricUIManager::javaobject> jFabricUIManager);
6061

62+
void commitViewTags(SurfaceId surfaceId, const std::vector<Tag> &tags);
63+
6164
jni::global_ref<JFabricUIManager::javaobject> javaUIManager_;
6265
std::shared_ptr<FabricMountingManager> mountingManager_;
6366
std::unique_ptr<ViewComponentDescriptor> viewComponentDescriptor_;

‎packages/react-native/ReactAndroid/src/test/java/com/facebook/react/fabric/FabricMountingManagerInstrumentationTest.kt‎

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@
88
package com.facebook.react.fabric
99

1010
import android.view.View
11+
import android.view.ViewGroup
1112
import androidx.test.ext.junit.runners.AndroidJUnit4
1213
import androidx.test.platform.app.InstrumentationRegistry
1314
import com.facebook.react.ReactRootView
@@ -408,6 +409,27 @@ class FabricMountingManagerInstrumentationTest {
408409
return surfaceHandler
409410
}
410411

412+
/**
413+
* A bridgeless surface can commit from the JavaScript thread before its start returns. That
414+
* revision mounts before the next one.
415+
*/
416+
@Test
417+
fun native_startSurfaceWithSurfaceHandler_pullModel_mountsCommitDuringStartInOrder() {
418+
overrideCxxFlags(pullModel = true)
419+
val fabricUIManager = createFabricUIManager()
420+
val helper = registerBinding(fabricUIManager)
421+
helper.commitViewsOnSurfaceStart(intArrayOf(42))
422+
var mountedTags = emptyList<Int>()
423+
runOnUiThread {
424+
val surfaceHandler = startSurfaceWithHandler(fabricUIManager)
425+
helper.commitViews(surfaceHandler.surfaceId, intArrayOf(43, 42))
426+
val rootView = fabricUIManager.resolveView(surfaceHandler.surfaceId) as ViewGroup
427+
mountedTags = (0 until rootView.childCount).map { rootView.getChildAt(it).id }
428+
}
429+
430+
assertThat(mountedTags).containsExactly(43, 42)
431+
}
432+
411433
/**
412434
* A restarted bridgeless surface keeps its id and is registered before it starts, so a pull or a
413435
* mount report left from its previous run can reach it before it runs. Both skip it.

‎packages/react-native/ReactAndroid/src/test/java/com/facebook/react/fabric/FabricMountingManagerTestHelper.kt‎

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -49,6 +49,12 @@ private constructor(fabricUIManager: FabricUIManager) : HybridClassBase() {
4949

5050
external fun commitViews(surfaceId: Int, tags: IntArray)
5151

52+
/**
53+
* Commits the views from another thread, as the JavaScript thread would, while a surface starts.
54+
* The start waits for the commit.
55+
*/
56+
external fun commitViewsOnSurfaceStart(tags: IntArray)
57+
5258
/**
5359
* Stops the surface but leaves it in the FabricUIManagerBinding's registry, in the state a
5460
* registered surface has before it starts.

0 commit comments

Comments
 (0)