You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
{{ message }}
Repository navigation
Commit b703a10
Browse filesBrowse the repository at this point in the historyBrowse files
Pull model: mount the commits of starting and stopping surfaces (#59030)
Summary:
With `enableMountingCoordinatorPullModelAndroid`, a commit tells Java only its surface id. At the next mount dispatch, `FabricUIManagerBinding::pullAndExecuteTransaction` looks the surface's `MountingCoordinator` up in the binding's surface registry. A surface is not in the registry while it starts or stops:
- The transaction that stops a surface is never pulled, because `stopSurfaceWithSurfaceHandler` removes the surface from the registry before `SurfaceHandler::stop()` commits the empty tree. Mounting override delegates such as LayoutAnimations and C++ Animated never see those Deletes. With `enableLayoutAnimationsOnAndroid`, a layout animation that runs when its surface stops stays in flight, and `FabricUIManager` calls `driveCxxAnimations` on every frame until another surface pulls a transaction.
- A commit that a bridgeless surface makes before `startSurfaceWithSurfaceHandler` registers it is not mounted until the surface commits again.
- The legacy `stopSurface(int)` holds the registry lock across `stop()`. On the UI thread, the stop commit pulls inline and blocks on the same lock, so the app freezes.
iOS does not look the surface up. `RCTMountingManager` captures the `MountingCoordinator` that reported the commit in the block it dispatches to the main queue, and pulls from it.
This PR does the same. `schedulerShouldRenderTransactions` keeps the reporting `MountingCoordinator` until the UI thread pulls, and `pullAndExecuteTransaction` pulls from it. Java still receives only the surface id. The surface registry, the start and stop functions, `reportMount` and the push model are unchanged.
As on iOS, a surface stopped on the UI thread has its stop transaction pulled during the stop commit. When it stops on another thread, its shadow tree is destroyed and its coordinator revoked before the UI thread pulls, so that transaction is not mounted. A pull of a stopped surface can log "Executing commit after surface N was stopped!" when a mounting override delegate still returns a transaction; iOS pulls these the same way.
Only one coordinator per surface id is kept. Stopping and starting the same `ReactSurface` on the same host reuses its id. If the stopped surface's commit has not been pulled yet, the restarted surface's coordinator replaces it and a warning is logged. The replaced coordinator is revoked and would pull nothing. Java does not mount such a restarted surface anyway: `MountingManager.startSurface` logs "Called startSurface more than once" and keeps the stopped surface's manager.
## Changelog:
[ANDROID] [FIXED] - Pull model: mount the commits of starting and stopping surfaces, and fix a freeze when the legacy `stopSurface(int)` runs on the UI thread
Test Plan:
New `FabricMountingManagerInstrumentationTest` cases drive a real `FabricUIManagerBinding`. The test helper runs no JavaScript and commits views to the surface's shadow tree itself. The pull-model cases run with the Props 2.0 flags and `enablePreallocatedPropsDiffOnInsertAndroid`.
| Test | Without the fix | With the fix |
| --- | --- | --- |
| `native_stopSurfaceWithSurfaceHandler_pullModel_pullsDeletes` | fails: no Deletes are pulled | passes |
| `native_stopSurface_pullModel_pullsDeletes` | hangs | passes |
| `native_stopSurfaceWithSurfaceHandler_pushModel_pullsDeletes` | passes | passes |
The push case is a control. The hang stops the run, so the bridgeless stop test was also run without the legacy one. With the fix, the whole class passes.
A one-off check, not committed, covered the start: a bridgeless surface started on a background thread commits a view, and the start is held before `startSurfaceWithSurfaceHandler` registers the surface. On #59029 alone the view is not mounted while the start is held. With this PR it is mounted by the next frame.
The test class has no Gradle target in the open-source repository, so I ran it on an Android emulator through a local androidTest source set:
```
./gradlew :packages:react-native:ReactAndroid:assembleReleaseAndroidTest -PreactNativeArchitectures=arm64-v8a
adb install -r -t packages/react-native/ReactAndroid/build/outputs/apk/androidTest/release/ReactAndroid-release-androidTest.apk
adb shell am instrument -w -e class com.facebook.react.fabric.FabricMountingManagerInstrumentationTest \
com.facebook.react.tests.gradle/androidx.test.runner.AndroidJUnitRunner
```
I also checked the effect in RNTester with a temporary hook, not part of this PR. The Playground starts a 60 s `LayoutAnimation`, then `ReactDelegate.unloadApp()` stops the surface on the UI thread while the activity stays resumed. Props 2.0 and `enableLayoutAnimationsOnAndroid` were on:
| Run | After the stop |
| --- | --- |
| Pull, without this PR | no `onAllAnimationsComplete`; `driveCxxAnimations` keeps running on every frame |
| Pull, with this PR | `onAllAnimationsComplete` right after the stop, then no more driven frames |
| Push, with this PR | `onAllAnimationsComplete` right after the stop, as before |
Pulled By:
bartlomiejbloniarz
bartlomiejbloniarz
Differential Revision: D124966432
Copy file name to clipboardExpand all lines: packages/react-native/ReactAndroid/src/main/jni/react/fabric/test_helper/FabricMountingManagerTestHelper.cpp
Copy file name to clipboardExpand all lines: packages/react-native/ReactAndroid/src/main/jni/react/fabric/test_helper/FabricMountingManagerTestHelper.h
0 commit comments