Defer the pull model's synchronous mount batch until the root view is attached - #58152
Open
bartlomiejbloniarz wants to merge 1 commit into
Open
Defer the pull model's synchronous mount batch until the root view is attached#58152bartlomiejbloniarz wants to merge 1 commit into
bartlomiejbloniarz wants to merge 1 commit into
Conversation
… attached
Summary:
Two crashes can occur when a synchronous mount batch runs before its root view is attached. Both need the same bad state: a `ViewState` for a tag is present in `tagToViewState`, but its `view` field is null.
## Cause
A surface can render before its root view is attached. While the root view is not attached, `MountItemDispatcher.executeOrEnqueue` defers every mount item into `SurfaceMountingManager.onViewAttachMountItems`. The pull model does not defer one of them: `FabricUIManager.scheduleMountItem(synchronous = true)` calls `mountItem.execute()` directly.
That gives this sequence for a tag `T`:
1. **C++ claims the tag first.** `preallocateShadowView` puts `T` into `allocatedViewRegistry_`. It does this before it calls Java.
2. **Java does not create the view.** The `PreAllocateViewMountItem` for `T` is deferred, because `isWaitingForViewAttach` is true. No `ViewState` exists for `T`.
3. **C++ omits the Create instruction.** `executeMount` finds `T` in `allocatedViewTags`, so it does not add a Create for `T`.
4. **The mount batch runs too early.** The batch is not deferred, so it runs while the root view is still not attached. It has no Create for `T`, but it has an `UpdateEventEmitter`. `updateEventEmitter` calls `tagToViewState.getOrPut(T) { ViewState(T) }`, which makes a `ViewState` with a null `view`.
5. **The preallocation is cancelled.** The root view attaches and the deferred `PreAllocateViewMountItem` runs. `preallocateView` finds a `ViewState` for `T` and returns. `T` now has no view, and no Create will come.
The next `updateState` or `updateOverflowInset` for `T` throws.
## Fix
Apply the same attach barrier to the synchronous batch that every other mount item already obeys. If the root view is not attached, put the batch in the dispatcher queue instead of running it inline. The preallocations then run first, and the batch runs after the root view is attached.
Only `pullAndExecuteTransaction` passes `synchronous = true`, so the push model never reaches this path.
Differential Revision: D117519782
|
@bartlomiejbloniarz has exported this pull request. If you are a Meta employee, you can view the originating Diff in D117519782. |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary:
Two crashes can occur when a synchronous mount batch runs before its root view is attached. Both need the same bad state: a
ViewStatefor a tag is present intagToViewState, but itsviewfield is null.Cause
A surface can render before its root view is attached. While the root view is not attached,
MountItemDispatcher.executeOrEnqueuedefers every mount item intoSurfaceMountingManager.onViewAttachMountItems. The pull model does not defer one of them:FabricUIManager.scheduleMountItem(synchronous = true)callsmountItem.execute()directly.That gives this sequence for a tag
T:preallocateShadowViewputsTintoallocatedViewRegistry_. It does this before it calls Java.PreAllocateViewMountItemforTis deferred, becauseisWaitingForViewAttachis true. NoViewStateexists forT.executeMountfindsTinallocatedViewTags, so it does not add a Create forT.T, but it has anUpdateEventEmitter.updateEventEmittercallstagToViewState.getOrPut(T) { ViewState(T) }, which makes aViewStatewith a nullview.PreAllocateViewMountItemruns.preallocateViewfinds aViewStateforTand returns.Tnow has no view, and no Create will come.The next
updateStateorupdateOverflowInsetforTthrows.Fix
Apply the same attach barrier to the synchronous batch that every other mount item already obeys. If the root view is not attached, put the batch in the dispatcher queue instead of running it inline. The preallocations then run first, and the batch runs after the root view is attached.
Only
pullAndExecuteTransactionpassessynchronous = true, so the push model never reaches this path.Differential Revision: D117519782