[Android][Fabric] Create mutation missing from an unflatten reparenting batch under remount churn — production evidence for the #47960 class (surface-killing)
Dieses Issue hat noch niemand übernommen.
- Vorherrschende Sprache
- C++
- Sterne
- 127k
- Forks
- 25.3k
- Ø Merge
- 1 T. 23 Std.
- Gemergte PRs (30 T.)
- 4
Beschreibung
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).
Beitragsleitfaden
Erste Schritte
- Lies das ganze Issue und danach den Beitragsleitfaden des Projekts.
- Schreib ins Issue, dass du es übernimmst — das erspart doppelte Arbeit.
- Forke das Repository und arbeite in einem Branch.
- Öffne einen Pull Request, der die Issue-Nummer nennt.
Rechercherichtung
Beginne bei MountingCoordinator::pullTransaction und verfolge das Zusammenführen von Transaktionen bis zu MountItemDispatcher.dispatchMountItems und SurfaceMountingManager.addViewAt, wobei die aufgezeichnete Batch-Sequenz als fehlschlagender Fall verwendet wird. Reproduziere das flatten/unflatten-Remount-Churning unter Android; als erledigt gilt die Aufgabe, wenn kein Batch ein nicht erstelltes Tag einfügt und ein fehlender viewState die Instanz nicht abbaut. Ein minimaler eigenständiger Repro ist noch nicht verfügbar.
Vom Indexierungsmodell aus dem Issue-Text verfasst.
Bewertung
- Tech-Stack
- android, cpp, react-native
- Bereich
- mobile
- Issue-Typ
- Bug
- Schwierigkeit
- 5/5
- Geschätzter Aufwand
- Über eine Woche
- Aktivitätsstatus
- Aktiv
- Klarheit
- Größtenteils klar
- Anfängerfreundlichkeit
- 35/100