Skip to content

iOS: bound safe_area()'s boot-path dispatch_sync to avoid a launch deadlock - #74

Merged
GenericJam merged 3 commits into
masterfrom
fix/safearea-boot-timeout
Aug 27, 2026
Merged

iOS: bound safe_area()'s boot-path dispatch_sync to avoid a launch deadlock#74
GenericJam merged 3 commits into
masterfrom
fix/safearea-boot-timeout

Conversation

@GenericJam

Copy link
Copy Markdown
Owner

Summary

  • Mob.Screen.init/1 calls safe_area() synchronously before the first screen ever mounts. A plain dispatch_sync to the main queue there is a deadlock risk if the main thread hasn't reached an idle run-loop tick yet by that point.
  • Root-caused via an Apple App Store rejection on sloppy_joe: "the app kept loading indefinitely on launch," reproduced by the reviewer on an iPad Air 11-inch (M3) / iPadOS 26.5.2. The BEAM boot thread was blocking forever inside this NIF call, so the app never finished launching.
  • Switch to dispatch_async + a semaphore bounded to 2s, falling back to zero insets on timeout — a screen with wrong insets once is a far smaller bug than an app that never boots. Also mark the NIF dirty (ERL_NIF_DIRTY_JOB_IO_BOUND) so a slow main thread can no longer stall a regular BEAM scheduler either.

Test plan

  • xcrun clang-format --dry-run -Werror ios/mob_nif.m — clean
  • Verified via sloppy_joe (pointed mob_dir at this branch): app boots correctly on both iPhone and iPad simulators, before and after forcing UIDeviceFamily=[1] on the installed bundle (mirroring the App-Store-only device-family patch)
  • Real iPad hardware verification (pending — simulator can't accurately reproduce the iPhone-compatibility-mode window a real device uses for a UIDeviceFamily=[1] app)

🤖 Generated with Claude Code

GenericJamand others added 3 commits July 27, 2026 14:01
…adlock
Mob.Screen.init/1 calls safe_area() synchronously before the first screen
mounts. A plain dispatch_sync to the main queue there is a deadlock risk if
the main thread hasn't reached an idle run-loop tick yet — observed via a
device-family/compatibility-mode boot hang on iPad. Switch to dispatch_async
+ a bounded semaphore wait, falling back to zero insets on timeout, and mark
the NIF dirty (IO-bound) so a slow main thread can no longer stall a regular
BEAM scheduler either.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
…k screen
If BEAM boot doesn't produce a first screen within 15s, surface an explicit
"Startup Error" state instead of leaving the black startup spinner running
forever. An indefinite spinner and a persistent error look identical to
"frozen" from the outside — this makes any future boot-path stall
diagnosable (and visibly not "blank") rather than ambiguous.
Follow-up to the safe_area() dispatch_sync fix: that closed the one known
boot-path deadlock, but a second App Store review on the fixed build still
reported a black/blank launch on iPad. Root-cause without physical iPad
hardware isn't possible from here, so this is a defensive net for whatever
is still stalling boot on that device/OS combination, verified not to
false-positive on a normal boot (simulator, both iPhone and iPad).
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
If the watchdog fires, show "Version <CFBundleShortVersionString>
(<CFBundleVersion>)" alongside the error text — a cheap way to confirm
whoever sees it (us, a tester, a reviewer) is actually looking at the
build they think they are, rather than a stale cached copy.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
@GenericJam
GenericJam merged commit cbcc093 into masterAug 27, 2026
4 checks passed
@GenericJam
GenericJam deleted the fix/safearea-boot-timeout branch August 27, 2026 20:01
GenericJam added a commit that referenced this pull request Aug 28, 2026
…eadlock fix
Carries three merged changes since 0.7.31:
- #94 intrinsic Sheet detents ([:content] / [{:content, max_height: n}]) with
canonical validation through Mob.UI.normalize_sheet_detents!/1 re-applied at
the renderer boundary, iOS content measurement with capping, internal
scrolling and geometry re-clamping, composite Box accessibility (label,
explicit button role, disabled semantics), and Sheet in both platform tag
manifests.
- #74 the iOS launch deadlock behind an App Store rejection — safe_area()'s
unbounded dispatch_sync on the boot path could block the BEAM thread forever
so the app never finished launching.
- #78 iOS glass tint from the node background, and left-aligned fill_width rows.
The changelog records two known limitations rather than leaving them to be
discovered: a scrollable child inside a content detent expands instead of
scrolling independently (it reports intrinsic height), and a content sheet
presents at :medium for one frame before resizing, since content height is
only knowable after presentation.
Preflight: mix format, credo --strict, compile --warnings-as-errors, 1119
tests, git diff --check, and the native frame-registry harness all clean.
The Sheet work was additionally adversarially reviewed and device-verified on
an iPhone 17 Pro simulator (hug, cap, internal scroll, safe-area clearance)
and the Box/Android half on a physical Moto G.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Sign up for freeto join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant

@GenericJam