Fabric: schedulerDidFinishTransaction picks oldest (not newest) pending transaction as merge target, corrupting mount order
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
Description
FabricUIManagerBinding::schedulerDidFinishTransaction (Android, ReactAndroid/src/main/jni/react/fabric/FabricUIManagerBinding.cpp) selects which pending mounting transaction an incoming transaction should merge into using a forward search:
auto pendingTransaction = std::find_if(
pendingTransactions_.begin(),
pendingTransactions_.end(),
[&](const auto& transaction) {
return transaction.getSurfaceId() == mountingTransaction->getSurfaceId();
});
if (pendingTransaction != pendingTransactions_.end() &&
pendingTransaction->canMergeWith(*mountingTransaction)) {
pendingTransaction->mergeWith(std::move(*mountingTransaction));
} else {
pendingTransactions_.push_back(std::move(*mountingTransaction));
}
std::find_if over begin()/end() returns the oldest queued transaction for a surface. Whenever a surface has more than one pending transaction at once — which can legitimately happen any time MountingTransaction::canMergeWith refuses a merge between two adjacent transactions — this picks the wrong merge target.
Repro scenario
Given pending transactions T1, T2 for the same surface (already queued separately because canMergeWith refused to combine them), and an incoming T3:
T3was diffed by the renderer against shadow-tree state that already includesT2.- The forward search finds
T1first and mergesT3into it, producing[T1+T3, T2], which executes asT1 → T3 → T2. - But
T3was never diffed against a tree withoutT2in it — running it beforeT2desyncs the native view tree from the shadow tree that produced the diff.
This manifests as native-tree/shadow-tree divergence at mount-apply time: an insert lands at an index the real parent doesn't have (IndexOutOfBoundsException in addViewAt), or a remove resolves a stale parent tag that's no longer the expected ViewGroup (IllegalStateException in removeViewAt).
Expected behavior
A new transaction should only ever merge into the most recently queued pending transaction for its surface — the only one whose diffed-against tree state actually reflects everything already queued. That means the merge-target lookup should search from the back (rbegin()/rend()) instead of the front (begin()/end()).
Fix
Three-token change:
auto pendingTransaction = std::find_if(
pendingTransactions_.rbegin(),
pendingTransactions_.rend(),
[&](const auto& transaction) {
return transaction.getSurfaceId() == mountingTransaction->getSurfaceId();
});
if (pendingTransaction != pendingTransactions_.rend() &&
pendingTransaction->canMergeWith(*mountingTransaction)) {
...
Environment
React Native 0.86.0, Android (Fabric), observed downstream in a fork carrying an additional local patch that makes canMergeWith refuse certain merges (a Delete↔Create tag-pairing guard), which is what surfaces multi-entry pendingTransactions_ queues in practice. The underlying merge-target selection bug is present in schedulerDidFinishTransaction upstream regardless of that local patch — any code path that causes canMergeWith to refuse a merge (including future upstream guards) would trigger the same divergence.
Reproducibility
No deterministic manual repro is provided here — the bug is timing/queue-state dependent. The mechanism is provable deterministically with a unit test against MountingTransaction directly (construct 3 transactions, assert execution order), without needing device timing.
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 dans ReactAndroid/src/main/jni/react/fabric/FabricUIManagerBinding.cpp et examinez schedulerDidFinishTransaction ainsi que MountingTransaction::canMergeWith. Construisez trois transactions dans un test unitaire déterministe, vérifiez l’ordre d’exécution de plusieurs transactions en attente et confirmez que la cible de la fusion est la transaction mise en file la plus récemment.
Rédigé par le modèle d'indexation à partir du texte de l'issue.
Évaluation
- Stack technique
- android, cpp, react-native
- Domaine
- mobile, mobile-dev
- Type d'issue
- Bug
- Difficulté
- 2/5
- Temps estimé
- 1-3 heures
- Activité
- Active
- Clarté
- Clairement spécifiée
- Accessibilité débutants
- 78/100