[Android][Fabric] Create mutation missing from an unflatten reparenting batch under remount churn — production evidence for the #47960 class (surface-killing)
Personne n'a encore pris cette issue.
- Langage dominant
- C++
- Étoiles
- 127k
- Forks
- 25.3k
- Merge moyen
- 1 j 23 h
- PR mergées (30 j)
- 4
Description
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).
Guide de contribution
Ouvrir le guide de contribution
Par où commencer
- Lisez l'issue en entier, puis le guide de contribution du projet.
- Signalez en commentaire que vous la prenez — cela évite que deux personnes fassent le même travail.
- Forkez le dépôt et travaillez sur une branche.
- Ouvrez une pull request qui référence le numéro de l'issue.
Piste de recherche
Commencez par MountingCoordinator::pullTransaction et suivez la fusion des transactions jusqu’à MountItemDispatcher.dispatchMountItems et SurfaceMountingManager.addViewAt, en utilisant la séquence de batch capturée comme cas d’échec. Reproduisez le churn de remount flatten/unflatten sur Android ; le travail est terminé lorsqu’aucun batch n’insère de tag non créé et qu’un viewState manquant ne démonte pas l’instance. Un repro autonome minimal n’est pas encore disponible.
Rédigé par le modèle d'indexation à partir du texte de l'issue.
Évaluation
- Stack technique
- android, cpp, react-native
- Domaine
- mobile
- Type d'issue
- Bug
- Difficulté
- 5/5
- Temps estimé
- Plus d'une semaine
- Activité
- Active
- Clarté
- Plutôt claire
- Accessibilité débutants
- 35/100