Skip to content

Defer the pull model's synchronous mount batch until the root view is attached - #58152

Open
bartlomiejbloniarz wants to merge 1 commit into
mainfrom
export-D117519782
Open

Defer the pull model's synchronous mount batch until the root view is attached#58152
bartlomiejbloniarz wants to merge 1 commit into
mainfrom
export-D117519782

Conversation

@bartlomiejbloniarz

Copy link
Copy Markdown
Contributor

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

… 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
@meta-cla meta-cla Bot added the CLA Signed This label is managed by the Facebook bot. Authors need to sign the CLA before a PR can be reviewed. label Aug 26, 2026
@meta-codesync

meta-codesync Bot commented Aug 26, 2026

Copy link
Copy Markdown

@bartlomiejbloniarz has exported this pull request. If you are a Meta employee, you can view the originating Diff in D117519782.

@facebook-github-tools facebook-github-tools Bot added p: Software Mansion Partner: Software Mansion Partner labels Aug 26, 2026
@facebook-github-tools facebook-github-tools Bot added the p: Facebook Partner: Facebook label Aug 26, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

CLA Signed This label is managed by the Facebook bot. Authors need to sign the CLA before a PR can be reviewed. meta-exported p: Facebook Partner: Facebook p: Software Mansion Partner: Software Mansion Partner

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant