react / react/react-native

Fabric: schedulerDidFinishTransaction picks oldest (not newest) pending transaction as merge target, corrupting mount order

Đang mở Phù hợp với người mới
#58,175 1 bình luận 0 reaction 0 người được giao Xem trên GitHub

Chưa có ai nhận issue này.

Needs: Author Feedback Needs: Repro
Ngôn ngữ chính
C++
Star
127k
Fork
25.3k
Merge trung bình
1 ngày 23 giờ
Pull request đã merge (30 ngày)
4

Mô tả

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:

  • T3 was diffed by the renderer against shadow-tree state that already includes T2.
  • The forward search finds T1 first and merges T3 into it, producing [T1+T3, T2], which executes as T1 → T3 → T2.
  • But T3 was never diffed against a tree without T2 in it — running it before T2 desyncs 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 DeleteCreate 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.

Hướng dẫn đóng góp

Mở hướng dẫn đóng góp

Bắt đầu từ đâu

  1. Đọc hết issue, rồi đọc hướng dẫn đóng góp của dự án.
  2. Bình luận trên issue rằng bạn sẽ nhận — tránh hai người làm cùng một việc.
  3. Fork repository và làm thay đổi trên một nhánh.
  4. Mở pull request có tham chiếu số hiệu của issue.

Hướng nghiên cứu

Bắt đầu trong ReactAndroid/src/main/jni/react/fabric/FabricUIManagerBinding.cpp và kiểm tra schedulerDidFinishTransaction cùng với MountingTransaction::canMergeWith. Tạo ba transaction trong một unit test xác định, xác minh thứ tự thực thi của nhiều transaction đang chờ xử lý và xác nhận rằng transaction đích của thao tác merge là transaction được đưa vào hàng đợi gần đây nhất.

Do mô hình lập chỉ mục viết ra từ nội dung của issue.

Đánh giá

Công nghệ
android, cpp, react-native
Lĩnh vực
mobile, mobile-dev
Loại issue
Lỗi
Độ khó
2/5
Thời gian dự kiến
1-3 giờ
Mức độ hoạt động
Sôi nổi
Độ rõ ràng
Đặc tả rõ ràng
Mức phù hợp với người mới
78/100

Nhận issue mới trong hộp thư của bạn

Bản tóm tắt ngắn những issue GitHub phù hợp với người mới.