Skip to content

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.)

Last updated · 470 distilled / 1,213 read