[iOS] Deadlock: measureContent dispatch_syncs to main while holding the shadow tree lock

オープン
#848 コメント 0 件 リアクション 0 件 担当者 0 名 GitHub で見る

メンテナーはふだん 3 日以内に返信

まだ誰も着手していません。

評価

難易度
4/5
見積もり時間
3〜5日
初心者へのやさしさ
48/100
issue の種類
バグ
明瞭さ
おおむね明確
活発さ
活発
技術スタック
objective-c, react-native
領域
mobile

調査の方向性

Start with EnrichedTextInputViewShadowNode::measureContent and EnrichedTextViewShadowNode to trace the synchronous main-queue measurement described in the report; then inspect EnrichedTextInputView and its measureSize: path. Reproduce by swiping back from a native-stack screen containing an EnrichedTextInput while the header is visible. Done when measurement avoids the described lock cycle and the reported freeze no longer reproduces.

索引モデルが issue の本文から書いたものです。

説明

Before submitting a new issue
  • I tested using the latest version of the library, as the bug might be already fixed.
  • I tested using a supported version of react native.
  • I checked for possible duplicate issues, with possible answers.
Bug summary

On iOS, EnrichedTextInputViewShadowNode::measureContent can deadlock the app. When it runs off the main thread it does a dispatch_sync to the main queue to call measureSize: on the component view. But measureContent runs during a commit while that thread holds the shadow tree lock. If the main thread is meanwhile committing state of its own, it waits on the same lock, and neither side can proceed:

  • Layout thread: holds the shadow tree commit lock → measureContent → dispatch_sync(main) → waits for main.
  • Main thread: react-native-screens' RNSScreenStackHeaderConfig commits header state from -viewDidLayoutSubviews → waits for the shadow tree lock.

The app hangs until the watchdog kills it (0x8BADF00D).

In our app this reproduces reliably by swiping back (the interactive pop gesture) from any native-stack screen that contains an EnrichedTextInput. During the gesture both screens are mounted and the header config commits from viewDidLayoutSubviews almost every frame, so a measurement of the input soon collides with it.

EnrichedTextViewShadowNode uses the same dispatch_sync pattern, so EnrichedText is likely affected too, though we have only hit it with the input.

Proposed fix

We patch this locally and have shipped it to production. The mounted path measures without ever blocking on the main thread:

  1. EnrichedTextInputView keeps an immutable NSAttributedString snapshot of its text storage, built with the same mock-character handling measureSize: uses. It's guarded by an os_unfair_lock and refreshed on the main thread in tryUpdatingHeight, just before the state update that triggers the re-measure.
  2. measureContent measures that snapshot on the layout thread (NSAttributedString's measuring APIs are thread safe, and the snapshot is immutable and unshared). If no snapshot exists yet, it returns a zero height instead of blocking. The view pushes a state update as soon as it has content, which forces a re-measure.

Unlike #339 (reverted in #417), this doesn't commit a fixed size through state, so the measurement follows width changes.

Library version

1.2.0 (also reproduced on 1.1.1)

Environment info
  • React Native 0.88.0, New Architecture (Fabric)
  • Expo SDK 58
  • react-native-screens 4.30.0 (native stack)
  • iOS 26 and 27, physical devices and simulator
Steps to reproduce
  1. Render an EnrichedTextInput inside a screen of a native-stack navigator (react-native-screens) that shows a header.
  2. Push that screen on top of another screen.
  3. Swipe back slowly with the interactive pop gesture, release, and repeat a few times.
  4. The app freezes; after several seconds iOS kills it with 0x8BADF00D. Pausing in the debugger shows the main thread waiting on the shadow tree mutex and the JS/layout thread inside dispatch_sync from EnrichedTextInputViewShadowNode::measureContent.
Reproducible example repository

We don't have a standalone reproduction repo yet; We can put one together if the steps above aren't enough to reproduce it.

主要言語
C
スター
1.4k
フォーク
74
平均マージ
3日 4時間
マージ済み PR(30日)
18

環境構築

  • Dockerfile・Docker Compose ファイルなし
  • プルリクエストのテンプレートあり
  • コントリビューションガイドなし

はじめの一歩

  1. issue を最後まで読み、次にプロジェクトのコントリビューションガイドを読みます。
  2. 着手することを issue にコメントします — 二人が同じ作業をするのを防げます。
  3. リポジトリをフォークし、ブランチを切って変更します。
  4. issue 番号を参照したプルリクエストを送ります。

software-mansion/react-native-enriched-html のほかの issue

software-mansion/react-native-enriched-html の issue をすべて見る

似ている issue

C の issue をもっと見る

新しい issue をメールで受け取る

初心者向けの GitHub issue を短くまとめたダイジェスト。