SUMMARY
On iOS (physical device, iPadOS 26.5), the AIR runtime's main thread intermittently
enters a permanently blocked state during normal application execution, while the
runtime's own internal DoActionsManager worker thread continues running. Once
blocked, NO further ActionScript executes: no ENTER_FRAME ticks, no flash.utils.Timer
callbacks (even ones with zero external dependencies), no URLLoader completion/error
events — nothing. The application appears completely frozen (a static white or blank
screen) and never recovers on its own. This happens non-deterministically: the same
binary, launched repeatedly against the same server/network conditions, sometimes
boots cleanly and sometimes freezes at this point, with no code-level trigger we've
been able to identify on the application side.
We captured this live with Xcode Instruments (Time Profiler, attached to the running
process during an active freeze) and obtained a fully symbolicated stack showing the
main thread blocked inside AIR's own native networking cleanup path.
ENVIRONMENT
- AIR SDK: 51.3.1.1 (HARMAN-maintained build)
(Note: 51.3.4.2 was evaluated and rejected for this project due to an unrelated
Keychain-signing regression on macOS — not itself the subject of this report.)
- Target: iOS, packaged via
adt -package -target ipa-debug
- Test device: iPad (A16), iPad15,7, iPadOS 26.5 (build 23F77), arm64e
- Host: macOS, Xcode 16.0 (17F113), xctrace 16.0
- App descriptor: standard, no block (that produces an
unrelated packaging error — "error 105: invalid value" — on any non-empty content,
worked around at the Info.plist level post-build, not part of this report)
- Networking: standard flash.net.URLLoader / URLRequest, wrapped in an in-house
HTTPRequest class with its own retry/timeout logic layered on top of URLLoader's
own COMPLETE/IO_ERROR/HTTP_STATUS/SECURITY_ERROR events
- Reproduces both on fresh installs and warm relaunches of an already-installed build
- Reproduces after a full device reboot (rules out accumulated device-side memory
pressure or leaked OS-level resources from repeated test cycles as the sole cause)
REPRODUCTION / FREQUENCY
Not consistently reproducible on demand. Across roughly 15-20 launches during one
extended debugging session, the freeze occurred at unpredictable points during
early application bootstrap — sometimes within the first ~1 second of the main
thread's own life, sometimes several seconds in, sometimes not at all (the app
boots and runs normally). No single code path, screen, or network endpoint
correlates with every occurrence. The one constant is the shape of the failure
once it happens: main thread stops being scheduled entirely; a specific AIR
runtime worker thread keeps running.
DIAGNOSTIC METHOD
- Launched the app fresh via
xcrun devicectl device process launch.
- Attached Instruments' Time Profiler via
xcrun xctrace record --attach <pid> --template "Time Profiler" --time-limit 60s while the app was visibly frozen
on a static white screen (confirmed by direct visual inspection of the device,
not inferred).
- Exported the raw
time-sample table and the aggregated, symbolicated
time-profile table via xcrun xctrace export.
- Cross-referenced against our own application-level diagnostics (a lightweight
fire-and-forget HTTP ping emitted at every major bootstrap milestone), which
independently confirmed zero further ActionScript execution of any kind for
the full 60-second recording window once the freeze began — including a
plain flash.utils.Timer we added specifically as an event-independent
watchdog, which never fired despite having no dependency on network state.
KEY FINDING #1 — Main Thread's last-known stack, state "Blocked"
Across the entire 60-second recording, "Main Thread" (0x890a, tid 35082) appears
exactly ONCE in the raw time-sample table — at t=00:00.329 — in thread-state
"Blocked". It never appears again in that table for the remainder of the
recording, and it never appears at all in the CPU time-profile table (which only
records on-core "Running" samples) — i.e., the main thread was never observed
running again after this single sample.
We symbolicated that stack against the app's own binary (statically-linked AIR
runtime symbols are present and unstripped in the debug build) using atos:
main
-[_CTPlayerTimerGlue _doPlay:]
CorePlayer::DoPlay(bool, bool)
CorePlayer::DoPlay_EnterFrameStart()
ASyncManager::EmptyTrash(CorePlayer*)
CocoaURLStreamProvider::~CocoaURLStreamProvider()
CocoaURLStreamProvider::~CocoaURLStreamProvider()
-[AIR_PlayerURLAdapter clearAdapter]
In plain terms: the main thread's own ENTER_FRAME tick (DoPlay_EnterFrameStart)
triggered ASyncManager::EmptyTrash, AIR's routine cleanup of discarded/completed
async objects. That cleanup was in the process of destructing a
CocoaURLStreamProvider (the native iOS backend behind flash.net.URLLoader /
URLStream), and that destructor's call into -[AIR_PlayerURLAdapter clearAdapter]
is where the thread stopped and never resumed.
We have the raw, unsymbolicated frame addresses for this exact sample available on
request (or in the attached trace) in case HARMAN's internal symbol maps resolve
additional inlined frames our local atos pass could not.
KEY FINDING #2 — DoActionsManager keeps running throughout the freeze
While the main thread is blocked, a separate thread named "Idle Worship" (tid
35139) — resolving to AIR's own DoActionsManager — continues cycling
throughout the ENTIRE freeze window, roughly every 100-800ms, alternating
between these symbolicated states:
DoActionsManager::ThreadBody()
DoActionsManager::GetActionsThreadMessage(ActionsThreadMessage*)
TThreadWait::DoSleep(int)
TSafeLock::~TSafeLock()
TSafeThread::RunHelper() / TSafeThread::Run(EThreadType)
PlatformCriticalSection::PlatformEnter()
CoreCriticalSectionBase::Enter() / RemoveFromAbortList()
MMgc::EnterFrame::AddAbortUnwindObject(MMgc::AbortUnwindObject*)
This thread is alive, taking and releasing its own critical sections, and
touching the MMgc garbage collector's EnterFrame bookkeeping — but this activity
never unblocks the main thread. Whatever lock or condition the main thread is
waiting on inside the CocoaURLStreamProvider/AIR_PlayerURLAdapter teardown path
is apparently never satisfied by this thread's own progress.
WHAT WE RULED OUT
- Not a server-side issue: Django access logs confirm the backend received and
correctly responded to every in-flight request at the time of each freeze
(including one case where a request that our own client-side watchdog reported
as "pending" for 170+ seconds had, in fact, already been answered by the
server in milliseconds).
- Not the application's own retry/error-handling logic failing: our HTTPRequest
wrapper has correct, tested retry/backoff/fatal-alert logic gated on URLLoader's
COMPLETE/IO_ERROR/HTTP_STATUS/SECURITY_ERROR events — but none of those events,
nor a plain independent Timer, ever fire once the freeze begins, because the
main thread that would dispatch them is not running at all.
- Not app backgrounding / screen lock: visually confirmed the device screen was
on, unlocked, and the app was the active foreground app at the moment of at
least one captured freeze.
- Not a one-time device state issue: reproduced again after a full physical
device reboot.
- Not a deliberate application-level "give up" / kill-switch: audited the
application's own code for anything that would intentionally stop the frame
loop, zero the stage frame rate, or disable all timers as a group; found
nothing that does this.
SUSPECTED ROOT CAUSE
A race or deadlock inside the AIR iOS runtime's native networking teardown path,
specifically between:
ASyncManager::EmptyTrash destructing a CocoaURLStreamProvider on the main
thread during a normal ENTER_FRAME tick, and
- whatever
-[AIR_PlayerURLAdapter clearAdapter] needs to acquire or wait on
to complete (a lock also touched by DoActionsManager's own critical-section
cycling is one plausible candidate, given DoActionsManager's continuous
Enter()/RemoveFromAbortList() activity throughout the freeze).
This would explain the non-determinism cleanly: it only manifests when the timing
of a URLLoader object's native-side teardown collides with this other thread's
lock cycling in just the wrong way — consistent with what we observed (heavy,
parallel network activity during app bootstrap; freeze incidence that varies
run-to-run with no single reproducible trigger).
IMPACT
This is a hard, unrecoverable freeze from the end user's perspective — no error,
no alert, no crash log, no console output reaches the device's visible UI. The
process remains alive (confirmed via devicectl device info processes) but is
permanently unresponsive. The only recovery is force-quitting and relaunching,
which may or may not hit the same race again.
WHAT WE CAN PROVIDE ON REQUEST
- The full .trace bundle from this capture (includes symbol stores, raw
time-sample table, and the aggregated time-profile table)
- The exact decimal/hex frame addresses for the Main Thread's blocked sample
- Our own application-level bootstrap diagnostics log correlated to the same
time window
- A minimal reproduction case, if useful — we can likely narrow this to "make N
concurrent URLLoader requests during early boot, then let them complete/be
discarded" if that would help HARMAN reproduce independently
Happy to answer follow-up questions or provide additional trace captures.
SUMMARY
On iOS (physical device, iPadOS 26.5), the AIR runtime's main thread intermittently
enters a permanently blocked state during normal application execution, while the
runtime's own internal DoActionsManager worker thread continues running. Once
blocked, NO further ActionScript executes: no ENTER_FRAME ticks, no flash.utils.Timer
callbacks (even ones with zero external dependencies), no URLLoader completion/error
events — nothing. The application appears completely frozen (a static white or blank
screen) and never recovers on its own. This happens non-deterministically: the same
binary, launched repeatedly against the same server/network conditions, sometimes
boots cleanly and sometimes freezes at this point, with no code-level trigger we've
been able to identify on the application side.
We captured this live with Xcode Instruments (Time Profiler, attached to the running
process during an active freeze) and obtained a fully symbolicated stack showing the
main thread blocked inside AIR's own native networking cleanup path.
ENVIRONMENT
(Note: 51.3.4.2 was evaluated and rejected for this project due to an unrelated
Keychain-signing regression on macOS — not itself the subject of this report.)
adt -package -target ipa-debugunrelated packaging error — "error 105: invalid value" — on any non-empty content,
worked around at the Info.plist level post-build, not part of this report)
HTTPRequest class with its own retry/timeout logic layered on top of URLLoader's
own COMPLETE/IO_ERROR/HTTP_STATUS/SECURITY_ERROR events
pressure or leaked OS-level resources from repeated test cycles as the sole cause)
REPRODUCTION / FREQUENCY
Not consistently reproducible on demand. Across roughly 15-20 launches during one
extended debugging session, the freeze occurred at unpredictable points during
early application bootstrap — sometimes within the first ~1 second of the main
thread's own life, sometimes several seconds in, sometimes not at all (the app
boots and runs normally). No single code path, screen, or network endpoint
correlates with every occurrence. The one constant is the shape of the failure
once it happens: main thread stops being scheduled entirely; a specific AIR
runtime worker thread keeps running.
DIAGNOSTIC METHOD
xcrun devicectl device process launch.xcrun xctrace record --attach <pid> --template "Time Profiler" --time-limit 60swhile the app was visibly frozenon a static white screen (confirmed by direct visual inspection of the device,
not inferred).
time-sampletable and the aggregated, symbolicatedtime-profiletable viaxcrun xctrace export.fire-and-forget HTTP ping emitted at every major bootstrap milestone), which
independently confirmed zero further ActionScript execution of any kind for
the full 60-second recording window once the freeze began — including a
plain flash.utils.Timer we added specifically as an event-independent
watchdog, which never fired despite having no dependency on network state.
KEY FINDING #1 — Main Thread's last-known stack, state "Blocked"
Across the entire 60-second recording, "Main Thread" (0x890a, tid 35082) appears
exactly ONCE in the raw time-sample table — at t=00:00.329 — in thread-state
"Blocked". It never appears again in that table for the remainder of the
recording, and it never appears at all in the CPU time-profile table (which only
records on-core "Running" samples) — i.e., the main thread was never observed
running again after this single sample.
We symbolicated that stack against the app's own binary (statically-linked AIR
runtime symbols are present and unstripped in the debug build) using
atos:In plain terms: the main thread's own ENTER_FRAME tick (
DoPlay_EnterFrameStart)triggered
ASyncManager::EmptyTrash, AIR's routine cleanup of discarded/completedasync objects. That cleanup was in the process of destructing a
CocoaURLStreamProvider(the native iOS backend behind flash.net.URLLoader /URLStream), and that destructor's call into
-[AIR_PlayerURLAdapter clearAdapter]is where the thread stopped and never resumed.
We have the raw, unsymbolicated frame addresses for this exact sample available on
request (or in the attached trace) in case HARMAN's internal symbol maps resolve
additional inlined frames our local atos pass could not.
KEY FINDING #2 — DoActionsManager keeps running throughout the freeze
While the main thread is blocked, a separate thread named "Idle Worship" (tid
35139) — resolving to AIR's own
DoActionsManager— continues cyclingthroughout the ENTIRE freeze window, roughly every 100-800ms, alternating
between these symbolicated states:
This thread is alive, taking and releasing its own critical sections, and
touching the MMgc garbage collector's EnterFrame bookkeeping — but this activity
never unblocks the main thread. Whatever lock or condition the main thread is
waiting on inside the CocoaURLStreamProvider/AIR_PlayerURLAdapter teardown path
is apparently never satisfied by this thread's own progress.
WHAT WE RULED OUT
correctly responded to every in-flight request at the time of each freeze
(including one case where a request that our own client-side watchdog reported
as "pending" for 170+ seconds had, in fact, already been answered by the
server in milliseconds).
wrapper has correct, tested retry/backoff/fatal-alert logic gated on URLLoader's
COMPLETE/IO_ERROR/HTTP_STATUS/SECURITY_ERROR events — but none of those events,
nor a plain independent Timer, ever fire once the freeze begins, because the
main thread that would dispatch them is not running at all.
on, unlocked, and the app was the active foreground app at the moment of at
least one captured freeze.
device reboot.
application's own code for anything that would intentionally stop the frame
loop, zero the stage frame rate, or disable all timers as a group; found
nothing that does this.
SUSPECTED ROOT CAUSE
A race or deadlock inside the AIR iOS runtime's native networking teardown path,
specifically between:
ASyncManager::EmptyTrashdestructing aCocoaURLStreamProvideron the mainthread during a normal ENTER_FRAME tick, and
-[AIR_PlayerURLAdapter clearAdapter]needs to acquire or wait onto complete (a lock also touched by DoActionsManager's own critical-section
cycling is one plausible candidate, given DoActionsManager's continuous
Enter()/RemoveFromAbortList() activity throughout the freeze).
This would explain the non-determinism cleanly: it only manifests when the timing
of a URLLoader object's native-side teardown collides with this other thread's
lock cycling in just the wrong way — consistent with what we observed (heavy,
parallel network activity during app bootstrap; freeze incidence that varies
run-to-run with no single reproducible trigger).
IMPACT
This is a hard, unrecoverable freeze from the end user's perspective — no error,
no alert, no crash log, no console output reaches the device's visible UI. The
process remains alive (confirmed via
devicectl device info processes) but ispermanently unresponsive. The only recovery is force-quitting and relaunching,
which may or may not hit the same race again.
WHAT WE CAN PROVIDE ON REQUEST
time-sample table, and the aggregated time-profile table)
time window
concurrent URLLoader requests during early boot, then let them complete/be
discarded" if that would help HARMAN reproduce independently
Happy to answer follow-up questions or provide additional trace captures.