[Android][Fabric] Create mutation missing from an unflatten reparenting batch under remount churn — production evidence for the #47960 class (surface-killing)
Nadie ha tomado este issue todavía.
- Lenguaje dominante
- C++
- Estrellas
- 127k
- Forks
- 25.3k
- Merge medio
- 1 d 23 h
- PR fusionados (30 d)
- 4
Descripción
Framing: this appears to be a concrete, reproducible, production-scale instance of #47960 ("Transaction merging can cause CREATE mutations to be effectively dropped", closed as not_planned) — filed with fresh evidence because the impact is a dead surface at ~2% of Android sessions, the graceful-handling attempts (#56654, #56389, #56376) are unmerged, and the differentiator-side fixes that DID land (LIS differ, parentTag fixes, present in 0.85.3) do not prevent it.
Summary
When a flattened (layout-only) View must be unflattened in a commit that races with remount churn of the same subtree, the emitted IntBufferBatchMountItem can contain the reparenting sequence for the materialized wrapper without its Create mutation. SurfaceMountingManager.addViewAt then throws RetryableMountingLayerException: Unable to find viewState for tag, and since batch items are not retried (only view commands are), MountItemDispatcher.dispatchMountItems rethrows and the host tears the instance down — on a production app this is a dead white screen (or a hard crash under expo-updates error recovery).
Evidence
Captured on device (RN 0.85.3, bridgeless, Galaxy S23 Ultra, Android 16). The failing batch, printed by MountItemDispatcher:
REMOVE [2180]->[14] @3 // existing child removed from its parent...
INSERT [2184]->[14] @3 // ...a NEW view takes its slot — but 2184 has no viewState:
INSERT [2180]->[2184] @0 // no Create/preallocation anywhere for it
UPDATE PROPS [2180]
UPDATE STATE [2180]
UPDATE LAYOUT [2184]->[14]: x:0 y:0 w:1080 h:2316
UPDATE LAYOUT [2180]->[2184]: x:0 y:0 w:1080 h:2316
UPDATE EVENTEMITTER [2184]
UPDATE EVENTEMITTER [2180]
printSurfaceState at crash time confirms tag 2184 is absent from the registry (526 views listed; 2184 not among them), i.e. the Create was never emitted or executed — this is not an ordering problem within the batch.
The shape is the classic unflatten sequence: child keeps its tag and reparents into the newly materialized wrapper. The wrapper here is a library-owned layout-only view (StyleSheet.absoluteFill + pointerEvents="box-none") around a native sheet component, whose sibling/outer wrapper toggles pointerEvents between 'none' and 'auto' — the prop change that makes it non-flattenable.
Causal confirmation: forcing the outermost wrapper real with collapsable={false} moved the identical missing-Create crash exactly one level down to the next flattenable wrapper in the ancestor chain (REMOVE [3010]->[3016]; INSERT [3014]->[3016]; INSERT [3010]->[3014] — 3016 now materialized and registered, 3014 missing). Pinning every wrapper in the chain eliminates the crash entirely (0/40 reproductions vs ~1/3 before).
Trigger conditions (from ~150 production events + device repro)
- Android, New Architecture (Fabric), RN 0.85.3 (production telemetry also on 0.85.x; the sub-signature first appeared for us after adopting native-stack screens, and scaled ~20x when a high-frequency bottom-sheet moved onto this wrapper shape).
- A flattenable wrapper whose subtree is rapidly remounted (portal entry torn down and recreated within a frame or two — e.g. reopening a sheet before the previous instance finished closing) while a prop change forces the wrapper to unflatten.
- Reproduces within ~3 attempts on device with that churn; the same UI used slowly does not reproduce.
Where the Create is likely lost
The differentiator emitting Insert-without-Create seems unlikely given the unflatten fixes already present in 0.85.3 (LIS differ #56094, parentTag fixes #50884/#56542 — all included, crash persists). The shape instead matches transaction merging (#44188 / #47960): the unflatten commit's Create lands in one transaction, the churn (portal entry unmount/remount within a frame or two, main thread busy with the sheet teardown) produces adjacent transactions, and the merge drops the Create while keeping the Insert/reparent. We have not instrumented MountingCoordinator::pullTransaction to prove the merge directly, but the trigger profile (only under rapid remount churn; never when the same UI is used slowly) is consistent with it.
Repro
Minimal standalone repro not yet extracted; the real-world trigger is @swmansion/react-native-bottom-sheet's portal host (issue filed there with the same evidence: software-mansion-labs/react-native-bottom-sheet#78, one-line fix in software-mansion-labs/react-native-bottom-sheet#79), where each portal entry is <View style={absoluteFill} pointerEvents="box-none"> around a native sheet, remounted under user churn. On a Galaxy S23 Ultra this reproduced within ~3 open/act/reopen cycles. Happy to work toward a minimal repro if the mutation traces above are not sufficient to locate the path.
Expected
A batch must never contain an Insert for a tag that no executed transaction has Created — regardless of transaction merging or concurrent remount churn. Secondarily: a missing viewState in the batch-mount path destroys the entire React instance in bridgeless mode (white screen, app unusable); the graceful-handling direction of #56654 would at least bound the blast radius.
Workaround
collapsable={false} on every flattenable ancestor wrapper of the affected subtree (prevents the flatten→unflatten transition entirely).
Guía de contribución
Primeros pasos
- Lee el issue completo y luego la guía de contribución del proyecto.
- Comenta en el issue que vas a ocuparte — evita que dos personas hagan lo mismo.
- Haz un fork del repositorio y trabaja en una rama.
- Abre un pull request que haga referencia al número del issue.
Línea de trabajo
Comienza en MountingCoordinator::pullTransaction y sigue la combinación de transacciones hasta MountItemDispatcher.dispatchMountItems y SurfaceMountingManager.addViewAt, usando la secuencia de batch capturada como caso que falla. Reproduce en Android el churn de remount de flatten/unflatten; se considera terminado cuando ningún batch inserta un tag no creado y un viewState ausente no desmonta la instancia. Aún no hay disponible una reproducción independiente mínima.
Escrito por el modelo de indexación a partir del texto del issue.
Evaluación
- Stack tecnológico
- android, cpp, react-native
- Área
- mobile
- Tipo de issue
- Error
- Dificultad
- 5/5
- Tiempo estimado
- Más de una semana
- Estado de actividad
- Activo
- Claridad
- Bastante claro
- Aptitud para principiantes
- 35/100