CONCEPT Cited by 1 source
Legacy native module main-queue deadlock¶
Definition¶
Legacy native module main-queue deadlock is a failure mode
in React Native on iOS where a legacy (bridge-era, non-
TurboModule) native module that has declared
requiresMainQueueSetup: true deadlocks on app launch when
animations are running on the main thread. The native
module's setup blocks waiting for the main thread; the main
thread is busy running the animation. Neither can proceed.
Symptom¶
The failure manifests as an "App Hang" — the iOS OS classifies the process as non-responsive and eventually terminates it, producing a production crash event (not a plain JS exception).
Shopify's operational detail (see sources/2025-09-12-shopify-migrating-to-react-natives-new-architecture):
- "Not easily reproducible in development" — development builds often avoid the specific concurrency that triggers the deadlock.
- Required closing and launching the app repeatedly for minutes to reproduce.
- Numerous production crash events in the real rollout, invisible in-house.
This is a canonical example of why production scale produces signal that pre-rollout testing cannot. (See also sources/2025-09-12-shopify-migrating-to-react-natives-new-architecture ANR-spike discussion.)
Fix¶
Set requiresMainQueueSetup: false on the module's bridge
declaration. Shopify's guidance: "If you still have legacy
native modules, make sure to set requiresMainQueueSetup to
false unless strictly necessary. In most cases, it is not."
The flag is a holdover from modules that genuinely needed
main-thread setup (e.g., touching UIKit at init time). Most
modules don't, but the default has historically been true
in older React Native templates.
Why the new architecture exposed it¶
In the old architecture, the concurrency shape was different enough that the deadlock didn't reliably trigger. Under the new architecture's synchronous rendering, the main-thread animation work overlaps more tightly with module loading, making the deadlock easier to hit in practice.
This mirrors the broader pattern — the new architecture doesn't cause these bugs; it exposes latent concurrency-timing assumptions that the old architecture happened to tolerate. (See concepts/state-batching, concepts/view-flattening for the same shape.)
Related¶
- systems/react-native — where the module lifecycle lives.
- concepts/state-batching — another latent-assumption exposure pattern triggered by the new architecture.
- concepts/view-flattening — same pattern, rendering-side.